Passed
Push — master ( 01ab6c...0597c0 )
by
unknown
39:34 queued 21:30
created

ContentObjectRenderer::getBorderAttr()   A

Complexity

Conditions 5
Paths 2

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 8
nc 2
nop 1
dl 0
loc 12
rs 9.6111
c 0
b 0
f 0
1
<?php
2
namespace TYPO3\CMS\Frontend\ContentObject;
3
4
/*
5
 * This file is part of the TYPO3 CMS project.
6
 *
7
 * It is free software; you can redistribute it and/or modify it under
8
 * the terms of the GNU General Public License, either version 2
9
 * of the License, or any later version.
10
 *
11
 * For the full copyright and license information, please read the
12
 * LICENSE.txt file that was distributed with this source code.
13
 *
14
 * The TYPO3 project - inspiring people to share!
15
 */
16
17
use Doctrine\DBAL\DBALException;
18
use Doctrine\DBAL\Driver\Statement;
19
use Psr\Container\ContainerInterface;
20
use Psr\Log\LoggerAwareInterface;
21
use Psr\Log\LoggerAwareTrait;
22
use Symfony\Component\Mime\Address;
23
use TYPO3\CMS\Core\Authentication\AbstractUserAuthentication;
24
use TYPO3\CMS\Core\Cache\CacheManager;
25
use TYPO3\CMS\Core\Context\Context;
26
use TYPO3\CMS\Core\Context\LanguageAspect;
27
use TYPO3\CMS\Core\Core\Environment;
28
use TYPO3\CMS\Core\Database\Connection;
29
use TYPO3\CMS\Core\Database\ConnectionPool;
30
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
31
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
32
use TYPO3\CMS\Core\Database\Query\QueryHelper;
33
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
34
use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
35
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
36
use TYPO3\CMS\Core\Html\HtmlParser;
37
use TYPO3\CMS\Core\Imaging\ImageManipulation\Area;
38
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
39
use TYPO3\CMS\Core\LinkHandling\LinkService;
40
use TYPO3\CMS\Core\Log\LogManager;
41
use TYPO3\CMS\Core\Mail\MailMessage;
42
use TYPO3\CMS\Core\Page\AssetCollector;
43
use TYPO3\CMS\Core\Resource\Exception;
44
use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
45
use TYPO3\CMS\Core\Resource\File;
46
use TYPO3\CMS\Core\Resource\FileInterface;
47
use TYPO3\CMS\Core\Resource\FileReference;
48
use TYPO3\CMS\Core\Resource\ProcessedFile;
49
use TYPO3\CMS\Core\Resource\ResourceFactory;
50
use TYPO3\CMS\Core\Service\DependencyOrderingService;
51
use TYPO3\CMS\Core\Service\FlexFormService;
52
use TYPO3\CMS\Core\Service\MarkerBasedTemplateService;
53
use TYPO3\CMS\Core\Site\SiteFinder;
54
use TYPO3\CMS\Core\TimeTracker\TimeTracker;
55
use TYPO3\CMS\Core\Type\BitSet;
56
use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
57
use TYPO3\CMS\Core\TypoScript\TypoScriptService;
58
use TYPO3\CMS\Core\Utility\ArrayUtility;
59
use TYPO3\CMS\Core\Utility\DebugUtility;
60
use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException;
61
use TYPO3\CMS\Core\Utility\GeneralUtility;
62
use TYPO3\CMS\Core\Utility\HttpUtility;
63
use TYPO3\CMS\Core\Utility\MailUtility;
64
use TYPO3\CMS\Core\Utility\MathUtility;
65
use TYPO3\CMS\Core\Utility\StringUtility;
66
use TYPO3\CMS\Core\Versioning\VersionState;
67
use TYPO3\CMS\Frontend\ContentObject\Exception\ContentRenderingException;
68
use TYPO3\CMS\Frontend\ContentObject\Exception\ExceptionHandlerInterface;
69
use TYPO3\CMS\Frontend\ContentObject\Exception\ProductionExceptionHandler;
70
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
71
use TYPO3\CMS\Frontend\Http\UrlProcessorInterface;
72
use TYPO3\CMS\Frontend\Imaging\GifBuilder;
73
use TYPO3\CMS\Frontend\Page\PageLayoutResolver;
74
use TYPO3\CMS\Frontend\Resource\FilePathSanitizer;
75
use TYPO3\CMS\Frontend\Service\TypoLinkCodecService;
76
use TYPO3\CMS\Frontend\Typolink\AbstractTypolinkBuilder;
77
use TYPO3\CMS\Frontend\Typolink\UnableToLinkException;
78
79
/**
80
 * This class contains all main TypoScript features.
81
 * This includes the rendering of TypoScript content objects (cObjects).
82
 * Is the backbone of TypoScript Template rendering.
83
 *
84
 * There are lots of functions you can use from your include-scripts.
85
 * The class is normally instantiated and referred to as "cObj".
86
 * 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.
87
 */
88
class ContentObjectRenderer implements LoggerAwareInterface
89
{
90
    use LoggerAwareTrait;
91
92
    /**
93
     * @var ContainerInterface
94
     */
95
    protected $container;
96
97
    /**
98
     * @var array
99
     */
100
    public $align = [
101
        'center',
102
        'right',
103
        'left'
104
    ];
105
106
    /**
107
     * stdWrap functions in their correct order
108
     *
109
     * @see stdWrap()
110
     * @var string[]
111
     */
112
    public $stdWrapOrder = [
113
        'stdWrapPreProcess' => 'hook',
114
        // this is a placeholder for the first Hook
115
        'cacheRead' => 'hook',
116
        // this is a placeholder for checking if the content is available in cache
117
        'setContentToCurrent' => 'boolean',
118
        'setContentToCurrent.' => 'array',
119
        'addPageCacheTags' => 'string',
120
        'addPageCacheTags.' => 'array',
121
        'setCurrent' => 'string',
122
        'setCurrent.' => 'array',
123
        'lang.' => 'array',
124
        'data' => 'getText',
125
        'data.' => 'array',
126
        'field' => 'fieldName',
127
        'field.' => 'array',
128
        'current' => 'boolean',
129
        'current.' => 'array',
130
        'cObject' => 'cObject',
131
        'cObject.' => 'array',
132
        'numRows.' => 'array',
133
        'preUserFunc' => 'functionName',
134
        'stdWrapOverride' => 'hook',
135
        // this is a placeholder for the second Hook
136
        'override' => 'string',
137
        'override.' => 'array',
138
        'preIfEmptyListNum' => 'listNum',
139
        'preIfEmptyListNum.' => 'array',
140
        'ifNull' => 'string',
141
        'ifNull.' => 'array',
142
        'ifEmpty' => 'string',
143
        'ifEmpty.' => 'array',
144
        'ifBlank' => 'string',
145
        'ifBlank.' => 'array',
146
        'listNum' => 'listNum',
147
        'listNum.' => 'array',
148
        'trim' => 'boolean',
149
        'trim.' => 'array',
150
        'strPad.' => 'array',
151
        'stdWrap' => 'stdWrap',
152
        'stdWrap.' => 'array',
153
        'stdWrapProcess' => 'hook',
154
        // this is a placeholder for the third Hook
155
        'required' => 'boolean',
156
        'required.' => 'array',
157
        'if.' => 'array',
158
        'fieldRequired' => 'fieldName',
159
        'fieldRequired.' => 'array',
160
        'csConv' => 'string',
161
        'csConv.' => 'array',
162
        'parseFunc' => 'objectpath',
163
        'parseFunc.' => 'array',
164
        'HTMLparser' => 'boolean',
165
        'HTMLparser.' => 'array',
166
        'split.' => 'array',
167
        'replacement.' => 'array',
168
        'prioriCalc' => 'boolean',
169
        'prioriCalc.' => 'array',
170
        'char' => 'integer',
171
        'char.' => 'array',
172
        'intval' => 'boolean',
173
        'intval.' => 'array',
174
        'hash' => 'string',
175
        'hash.' => 'array',
176
        'round' => 'boolean',
177
        'round.' => 'array',
178
        'numberFormat.' => 'array',
179
        'expandList' => 'boolean',
180
        'expandList.' => 'array',
181
        'date' => 'dateconf',
182
        'date.' => 'array',
183
        'strtotime' => 'strtotimeconf',
184
        'strtotime.' => 'array',
185
        'strftime' => 'strftimeconf',
186
        'strftime.' => 'array',
187
        'age' => 'boolean',
188
        'age.' => 'array',
189
        'case' => 'case',
190
        'case.' => 'array',
191
        'bytes' => 'boolean',
192
        'bytes.' => 'array',
193
        'substring' => 'parameters',
194
        'substring.' => 'array',
195
        'cropHTML' => 'crop',
196
        'cropHTML.' => 'array',
197
        'stripHtml' => 'boolean',
198
        'stripHtml.' => 'array',
199
        'crop' => 'crop',
200
        'crop.' => 'array',
201
        'rawUrlEncode' => 'boolean',
202
        'rawUrlEncode.' => 'array',
203
        'htmlSpecialChars' => 'boolean',
204
        'htmlSpecialChars.' => 'array',
205
        'encodeForJavaScriptValue' => 'boolean',
206
        'encodeForJavaScriptValue.' => 'array',
207
        'doubleBrTag' => 'string',
208
        'doubleBrTag.' => 'array',
209
        'br' => 'boolean',
210
        'br.' => 'array',
211
        'brTag' => 'string',
212
        'brTag.' => 'array',
213
        'encapsLines.' => 'array',
214
        'keywords' => 'boolean',
215
        'keywords.' => 'array',
216
        'innerWrap' => 'wrap',
217
        'innerWrap.' => 'array',
218
        'innerWrap2' => 'wrap',
219
        'innerWrap2.' => 'array',
220
        'preCObject' => 'cObject',
221
        'preCObject.' => 'array',
222
        'postCObject' => 'cObject',
223
        'postCObject.' => 'array',
224
        'wrapAlign' => 'align',
225
        'wrapAlign.' => 'array',
226
        'typolink.' => 'array',
227
        'wrap' => 'wrap',
228
        'wrap.' => 'array',
229
        'noTrimWrap' => 'wrap',
230
        'noTrimWrap.' => 'array',
231
        'wrap2' => 'wrap',
232
        'wrap2.' => 'array',
233
        'dataWrap' => 'dataWrap',
234
        'dataWrap.' => 'array',
235
        'prepend' => 'cObject',
236
        'prepend.' => 'array',
237
        'append' => 'cObject',
238
        'append.' => 'array',
239
        'wrap3' => 'wrap',
240
        'wrap3.' => 'array',
241
        'orderedStdWrap' => 'stdWrap',
242
        'orderedStdWrap.' => 'array',
243
        'outerWrap' => 'wrap',
244
        'outerWrap.' => 'array',
245
        'insertData' => 'boolean',
246
        'insertData.' => 'array',
247
        'postUserFunc' => 'functionName',
248
        'postUserFuncInt' => 'functionName',
249
        'prefixComment' => 'string',
250
        'prefixComment.' => 'array',
251
        'editIcons' => 'string',
252
        'editIcons.' => 'array',
253
        'editPanel' => 'boolean',
254
        'editPanel.' => 'array',
255
        'cacheStore' => 'hook',
256
        // this is a placeholder for storing the content in cache
257
        'stdWrapPostProcess' => 'hook',
258
        // this is a placeholder for the last Hook
259
        'debug' => 'boolean',
260
        'debug.' => 'array',
261
        'debugFunc' => 'boolean',
262
        'debugFunc.' => 'array',
263
        'debugData' => 'boolean',
264
        'debugData.' => 'array'
265
    ];
266
267
    /**
268
     * Class names for accordant content object names
269
     *
270
     * @var array
271
     */
272
    protected $contentObjectClassMap = [];
273
274
    /**
275
     * Loaded with the current data-record.
276
     *
277
     * If the instance of this class is used to render records from the database those records are found in this array.
278
     * The function stdWrap has TypoScript properties that fetch field-data from this array.
279
     *
280
     * @var array
281
     * @see start()
282
     */
283
    public $data = [];
284
285
    /**
286
     * @var string
287
     */
288
    protected $table = '';
289
290
    /**
291
     * Used for backup
292
     *
293
     * @var array
294
     */
295
    public $oldData = [];
296
297
    /**
298
     * If this is set with an array before stdWrap, it's used instead of $this->data in the data-property in stdWrap
299
     *
300
     * @var string
301
     */
302
    public $alternativeData = '';
303
304
    /**
305
     * Used by the parseFunc function and is loaded with tag-parameters when parsing tags.
306
     *
307
     * @var array
308
     */
309
    public $parameters = [];
310
311
    /**
312
     * @var string
313
     */
314
    public $currentValKey = 'currentValue_kidjls9dksoje';
315
316
    /**
317
     * This is set to the [table]:[uid] of the record delivered in the $data-array, if the cObjects CONTENT or RECORD is in operation.
318
     * Note that $GLOBALS['TSFE']->currentRecord is set to an equal value but always indicating the latest record rendered.
319
     *
320
     * @var string
321
     */
322
    public $currentRecord = '';
323
324
    /**
325
     * Set in RecordsContentObject and ContentContentObject to the current number of records selected in a query.
326
     *
327
     * @var int
328
     */
329
    public $currentRecordTotal = 0;
330
331
    /**
332
     * Incremented in RecordsContentObject and ContentContentObject before each record rendering.
333
     *
334
     * @var int
335
     */
336
    public $currentRecordNumber = 0;
337
338
    /**
339
     * Incremented in RecordsContentObject and ContentContentObject before each record rendering.
340
     *
341
     * @var int
342
     */
343
    public $parentRecordNumber = 0;
344
345
    /**
346
     * 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.
347
     *
348
     * @var array
349
     */
350
    public $parentRecord = [];
351
352
    /**
353
     * 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.
354
     *
355
     * @var array
356
     */
357
    public $checkPid_cache = [];
358
359
    /**
360
     * @var string|int
361
     */
362
    public $checkPid_badDoktypeList = PageRepository::DOKTYPE_RECYCLER;
363
364
    /**
365
     * This will be set by typoLink() to the url of the most recent link created.
366
     *
367
     * @var string
368
     */
369
    public $lastTypoLinkUrl = '';
370
371
    /**
372
     * DO. link target.
373
     *
374
     * @var string
375
     */
376
    public $lastTypoLinkTarget = '';
377
378
    /**
379
     * @var array
380
     */
381
    public $lastTypoLinkLD = [];
382
383
    /**
384
     * array that registers rendered content elements (or any table) to make sure they are not rendered recursively!
385
     *
386
     * @var array
387
     */
388
    public $recordRegister = [];
389
390
    /**
391
     * Additionally registered content object types and class names
392
     *
393
     * @var array
394
     */
395
    protected $cObjHookObjectsRegistry = [];
396
397
    /**
398
     * @var array
399
     */
400
    public $cObjHookObjectsArr = [];
401
402
    /**
403
     * Containing hook objects for stdWrap
404
     *
405
     * @var array
406
     */
407
    protected $stdWrapHookObjects = [];
408
409
    /**
410
     * Containing hook objects for getImgResource
411
     *
412
     * @var array
413
     */
414
    protected $getImgResourceHookObjects;
415
416
    /**
417
     * @var File Current file objects (during iterations over files)
418
     */
419
    protected $currentFile;
420
421
    /**
422
     * Set to TRUE by doConvertToUserIntObject() if USER object wants to become USER_INT
423
     * @var bool
424
     */
425
    public $doConvertToUserIntObject = false;
426
427
    /**
428
     * Indicates current object type. Can hold one of OBJECTTYPE_ constants or FALSE.
429
     * The value is set and reset inside USER() function. Any time outside of
430
     * USER() it is FALSE.
431
     * @var bool
432
     */
433
    protected $userObjectType = false;
434
435
    /**
436
     * @var array
437
     */
438
    protected $stopRendering = [];
439
440
    /**
441
     * @var int
442
     */
443
    protected $stdWrapRecursionLevel = 0;
444
445
    /**
446
     * @var TypoScriptFrontendController
447
     */
448
    protected $typoScriptFrontendController;
449
450
    /**
451
     * Indicates that object type is USER.
452
     *
453
     * @see ContentObjectRender::$userObjectType
454
     */
455
    const OBJECTTYPE_USER_INT = 1;
456
    /**
457
     * Indicates that object type is USER.
458
     *
459
     * @see ContentObjectRender::$userObjectType
460
     */
461
    const OBJECTTYPE_USER = 2;
462
463
    /**
464
     * @param TypoScriptFrontendController $typoScriptFrontendController
465
     * @param ContainerInterface $container
466
     */
467
    public function __construct(TypoScriptFrontendController $typoScriptFrontendController = null, ContainerInterface $container = null)
468
    {
469
        $this->typoScriptFrontendController = $typoScriptFrontendController;
470
        $this->contentObjectClassMap = $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'];
471
        $this->container = $container;
472
    }
473
474
    /**
475
     * Prevent several objects from being serialized.
476
     * If currentFile is set, it is either a File or a FileReference object. As the object itself can't be serialized,
477
     * we have store a hash and restore the object in __wakeup()
478
     *
479
     * @return array
480
     */
481
    public function __sleep()
482
    {
483
        $vars = get_object_vars($this);
484
        unset($vars['typoScriptFrontendController'], $vars['logger'], $vars['container']);
485
        if ($this->currentFile instanceof FileReference) {
0 ignored issues
show
introduced by
$this->currentFile is never a sub-type of TYPO3\CMS\Core\Resource\FileReference.
Loading history...
486
            $this->currentFile = 'FileReference:' . $this->currentFile->getUid();
487
        } elseif ($this->currentFile instanceof File) {
0 ignored issues
show
introduced by
$this->currentFile is always a sub-type of TYPO3\CMS\Core\Resource\File.
Loading history...
488
            $this->currentFile = 'File:' . $this->currentFile->getIdentifier();
0 ignored issues
show
Documentation Bug introduced by
It seems like 'File:' . $this->currentFile->getIdentifier() 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...
489
        } else {
490
            unset($vars['currentFile']);
491
        }
492
        return array_keys($vars);
493
    }
494
495
    /**
496
     * Restore currentFile from hash.
497
     * If currentFile references a File, the identifier equals file identifier.
498
     * If it references a FileReference the identifier equals the uid of the reference.
499
     */
500
    public function __wakeup()
501
    {
502
        if (isset($GLOBALS['TSFE'])) {
503
            $this->typoScriptFrontendController = $GLOBALS['TSFE'];
504
        }
505
        if ($this->currentFile !== null && is_string($this->currentFile)) {
0 ignored issues
show
introduced by
The condition is_string($this->currentFile) is always false.
Loading history...
506
            [$objectType, $identifier] = explode(':', $this->currentFile, 2);
507
            try {
508
                if ($objectType === 'File') {
509
                    $this->currentFile = GeneralUtility::makeInstance(ResourceFactory::class)->retrieveFileOrFolderObject($identifier);
510
                } elseif ($objectType === 'FileReference') {
511
                    $this->currentFile = GeneralUtility::makeInstance(ResourceFactory::class)->getFileReferenceObject($identifier);
512
                }
513
            } catch (ResourceDoesNotExistException $e) {
514
                $this->currentFile = null;
515
            }
516
        }
517
        $this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
518
        $this->container = GeneralUtility::getContainer();
519
    }
520
521
    /**
522
     * Allow injecting content object class map.
523
     *
524
     * This method is private API, please use configuration
525
     * $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'] to add new content objects
526
     *
527
     * @internal
528
     * @param array $contentObjectClassMap
529
     */
530
    public function setContentObjectClassMap(array $contentObjectClassMap)
531
    {
532
        $this->contentObjectClassMap = $contentObjectClassMap;
533
    }
534
535
    /**
536
     * Register a single content object name to class name
537
     *
538
     * This method is private API, please use configuration
539
     * $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'] to add new content objects
540
     *
541
     * @param string $className
542
     * @param string $contentObjectName
543
     * @internal
544
     */
545
    public function registerContentObjectClass($className, $contentObjectName)
546
    {
547
        $this->contentObjectClassMap[$contentObjectName] = $className;
548
    }
549
550
    /**
551
     * Class constructor.
552
     * Well, it has to be called manually since it is not a real constructor function.
553
     * 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.
554
     *
555
     * @param array $data The record data that is rendered.
556
     * @param string $table The table that the data record is from.
557
     */
558
    public function start($data, $table = '')
559
    {
560
        $this->data = $data;
561
        $this->table = $table;
562
        $this->currentRecord = $table !== ''
563
            ? $table . ':' . ($this->data['uid'] ?? '')
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
564
            : '';
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
565
        $this->parameters = [];
566
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['cObjTypeAndClass'] ?? [] as $classArr) {
567
            $this->cObjHookObjectsRegistry[$classArr[0]] = $classArr[1];
568
        }
569
        $this->stdWrapHookObjects = [];
570
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap'] ?? [] as $className) {
571
            $hookObject = GeneralUtility::makeInstance($className);
572
            if (!$hookObject instanceof ContentObjectStdWrapHookInterface) {
573
                throw new \UnexpectedValueException($className . ' must implement interface ' . ContentObjectStdWrapHookInterface::class, 1195043965);
574
            }
575
            $this->stdWrapHookObjects[] = $hookObject;
576
        }
577
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['postInit'] ?? [] as $className) {
578
            $postInitializationProcessor = GeneralUtility::makeInstance($className);
579
            if (!$postInitializationProcessor instanceof ContentObjectPostInitHookInterface) {
580
                throw new \UnexpectedValueException($className . ' must implement interface ' . ContentObjectPostInitHookInterface::class, 1274563549);
581
            }
582
            $postInitializationProcessor->postProcessContentObjectInitialization($this);
583
        }
584
    }
585
586
    /**
587
     * Returns the current table
588
     *
589
     * @return string
590
     */
591
    public function getCurrentTable()
592
    {
593
        return $this->table;
594
    }
595
596
    /**
597
     * Gets the 'getImgResource' hook objects.
598
     * The first call initializes the accordant objects.
599
     *
600
     * @return array The 'getImgResource' hook objects (if any)
601
     */
602
    protected function getGetImgResourceHookObjects()
603
    {
604
        if (!isset($this->getImgResourceHookObjects)) {
605
            $this->getImgResourceHookObjects = [];
606
            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImgResource'] ?? [] as $className) {
607
                $hookObject = GeneralUtility::makeInstance($className);
608
                if (!$hookObject instanceof ContentObjectGetImageResourceHookInterface) {
609
                    throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetImageResourceHookInterface::class, 1218636383);
610
                }
611
                $this->getImgResourceHookObjects[] = $hookObject;
612
            }
613
        }
614
        return $this->getImgResourceHookObjects;
615
    }
616
617
    /**
618
     * Sets the internal variable parentRecord with information about current record.
619
     * 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.
620
     *
621
     * @param array $data The record array
622
     * @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.
623
     * @internal
624
     */
625
    public function setParent($data, $currentRecord)
626
    {
627
        $this->parentRecord = [
628
            'data' => $data,
629
            'currentRecord' => $currentRecord
630
        ];
631
    }
632
633
    /***********************************************
634
     *
635
     * CONTENT_OBJ:
636
     *
637
     ***********************************************/
638
    /**
639
     * Returns the "current" value.
640
     * 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.
641
     * It's like "load accumulator" in the good old C64 days... basically a "register" you can use as you like.
642
     * 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.
643
     *
644
     * @return mixed The "current" value
645
     */
646
    public function getCurrentVal()
647
    {
648
        return $this->data[$this->currentValKey];
649
    }
650
651
    /**
652
     * Sets the "current" value.
653
     *
654
     * @param mixed $value The variable that you want to set as "current
655
     * @see getCurrentVal()
656
     */
657
    public function setCurrentVal($value)
658
    {
659
        $this->data[$this->currentValKey] = $value;
660
    }
661
662
    /**
663
     * Rendering of a "numerical array" of cObjects from TypoScript
664
     * Will call ->cObjGetSingle() for each cObject found and accumulate the output.
665
     *
666
     * @param array $setup array with cObjects as values.
667
     * @param string $addKey A prefix for the debugging information
668
     * @return string Rendered output from the cObjects in the array.
669
     * @see cObjGetSingle()
670
     */
671
    public function cObjGet($setup, $addKey = '')
672
    {
673
        if (!is_array($setup)) {
0 ignored issues
show
introduced by
The condition is_array($setup) is always true.
Loading history...
674
            return '';
675
        }
676
        $sKeyArray = ArrayUtility::filterAndSortByNumericKeys($setup);
677
        $content = '';
678
        foreach ($sKeyArray as $theKey) {
679
            $theValue = $setup[$theKey];
680
            if ((int)$theKey && strpos($theKey, '.') === false) {
681
                $conf = $setup[$theKey . '.'];
682
                $content .= $this->cObjGetSingle($theValue, $conf, $addKey . $theKey);
683
            }
684
        }
685
        return $content;
686
    }
687
688
    /**
689
     * Renders a content object
690
     *
691
     * @param string $name The content object name, eg. "TEXT" or "USER" or "IMAGE"
692
     * @param array $conf The array with TypoScript properties for the content object
693
     * @param string $TSkey A string label used for the internal debugging tracking.
694
     * @return string cObject output
695
     * @throws \UnexpectedValueException
696
     */
697
    public function cObjGetSingle($name, $conf, $TSkey = '__')
698
    {
699
        $content = '';
700
        // Checking that the function is not called eternally. This is done by interrupting at a depth of 100
701
        $this->getTypoScriptFrontendController()->cObjectDepthCounter--;
702
        if ($this->getTypoScriptFrontendController()->cObjectDepthCounter > 0) {
703
            $timeTracker = $this->getTimeTracker();
704
            $name = trim($name);
705
            if ($timeTracker->LR) {
706
                $timeTracker->push($TSkey, $name);
707
            }
708
            // Checking if the COBJ is a reference to another object. (eg. name of 'some.object =< styles.something')
709
            if (isset($name[0]) && $name[0] === '<') {
710
                $key = trim(substr($name, 1));
711
                $cF = GeneralUtility::makeInstance(TypoScriptParser::class);
712
                // $name and $conf is loaded with the referenced values.
713
                $confOverride = is_array($conf) ? $conf : [];
0 ignored issues
show
introduced by
The condition is_array($conf) is always true.
Loading history...
714
                [$name, $conf] = $cF->getVal($key, $this->getTypoScriptFrontendController()->tmpl->setup);
715
                $conf = array_replace_recursive(is_array($conf) ? $conf : [], $confOverride);
716
                // Getting the cObject
717
                $timeTracker->incStackPointer();
718
                $content .= $this->cObjGetSingle($name, $conf, $key);
719
                $timeTracker->decStackPointer();
720
            } else {
721
                $hooked = false;
722
                // Application defined cObjects
723
                if (!empty($this->cObjHookObjectsRegistry[$name])) {
724
                    if (empty($this->cObjHookObjectsArr[$name])) {
725
                        $this->cObjHookObjectsArr[$name] = GeneralUtility::makeInstance($this->cObjHookObjectsRegistry[$name]);
726
                    }
727
                    $hookObj = $this->cObjHookObjectsArr[$name];
728
                    if (method_exists($hookObj, 'cObjGetSingleExt')) {
729
                        $content .= $hookObj->cObjGetSingleExt($name, $conf, $TSkey, $this);
730
                        $hooked = true;
731
                    }
732
                }
733
                if (!$hooked) {
734
                    $contentObject = $this->getContentObject($name);
735
                    if ($contentObject) {
736
                        $content .= $this->render($contentObject, $conf);
737
                    } else {
738
                        // Call hook functions for extra processing
739
                        if ($name) {
740
                            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['cObjTypeAndClassDefault'] ?? [] as $className) {
741
                                $hookObject = GeneralUtility::makeInstance($className);
742
                                if (!$hookObject instanceof ContentObjectGetSingleHookInterface) {
743
                                    throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetSingleHookInterface::class, 1195043731);
744
                                }
745
                                /** @var ContentObjectGetSingleHookInterface $hookObject */
746
                                $content .= $hookObject->getSingleContentObject($name, (array)$conf, $TSkey, $this);
747
                            }
748
                        } else {
749
                            // Log error in AdminPanel
750
                            $warning = sprintf('Content Object "%s" does not exist', $name);
751
                            $timeTracker->setTSlogMessage($warning, 2);
752
                        }
753
                    }
754
                }
755
            }
756
            if ($timeTracker->LR) {
757
                $timeTracker->pull($content);
758
            }
759
        }
760
        // Increasing on exit...
761
        $this->getTypoScriptFrontendController()->cObjectDepthCounter++;
762
        return $content;
763
    }
764
765
    /**
766
     * Returns a new content object of type $name.
767
     * This content object needs to be registered as content object
768
     * in $this->contentObjectClassMap
769
     *
770
     * @param string $name
771
     * @return AbstractContentObject|null
772
     * @throws ContentRenderingException
773
     */
774
    public function getContentObject($name)
775
    {
776
        if (!isset($this->contentObjectClassMap[$name])) {
777
            return null;
778
        }
779
        $fullyQualifiedClassName = $this->contentObjectClassMap[$name];
780
        $contentObject = GeneralUtility::makeInstance($fullyQualifiedClassName, $this);
781
        if (!($contentObject instanceof AbstractContentObject)) {
782
            throw new ContentRenderingException(sprintf('Registered content object class name "%s" must be an instance of AbstractContentObject, but is not!', $fullyQualifiedClassName), 1422564295);
783
        }
784
        return $contentObject;
785
    }
786
787
    /********************************************
788
     *
789
     * Functions rendering content objects (cObjects)
790
     *
791
     ********************************************/
792
793
    /**
794
     * Renders a content object by taking exception and cache handling
795
     * into consideration
796
     *
797
     * @param AbstractContentObject $contentObject Content object instance
798
     * @param array $configuration Array of TypoScript properties
799
     *
800
     * @throws ContentRenderingException
801
     * @throws \Exception
802
     * @return string
803
     */
804
    public function render(AbstractContentObject $contentObject, $configuration = [])
805
    {
806
        $content = '';
807
808
        // Evaluate possible cache and return
809
        $cacheConfiguration = $configuration['cache.'] ?? null;
810
        if ($cacheConfiguration !== null) {
811
            unset($configuration['cache.']);
812
            $cache = $this->getFromCache($cacheConfiguration);
813
            if ($cache !== false) {
814
                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...
815
            }
816
        }
817
818
        // Render content
819
        try {
820
            $content .= $contentObject->render($configuration);
821
        } catch (ContentRenderingException $exception) {
822
            // Content rendering Exceptions indicate a critical problem which should not be
823
            // caught e.g. when something went wrong with Exception handling itself
824
            throw $exception;
825
        } catch (\Exception $exception) {
826
            $exceptionHandler = $this->createExceptionHandler($configuration);
827
            if ($exceptionHandler === null) {
828
                throw $exception;
829
            }
830
            $content = $exceptionHandler->handle($exception, $contentObject, $configuration);
831
        }
832
833
        // Store cache
834
        if ($cacheConfiguration !== null) {
835
            $key = $this->calculateCacheKey($cacheConfiguration);
836
            if (!empty($key)) {
837
                /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cacheFrontend */
838
                $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)->getCache('hash');
839
                $tags = $this->calculateCacheTags($cacheConfiguration);
840
                $lifetime = $this->calculateCacheLifetime($cacheConfiguration);
841
                $cacheFrontend->set($key, $content, $tags, $lifetime);
842
            }
843
        }
844
845
        return $content;
846
    }
847
848
    /**
849
     * Creates the content object exception handler from local content object configuration
850
     * or, from global configuration if not explicitly disabled in local configuration
851
     *
852
     * @param array $configuration
853
     * @return ExceptionHandlerInterface|null
854
     * @throws ContentRenderingException
855
     */
856
    protected function createExceptionHandler($configuration = [])
857
    {
858
        $exceptionHandler = null;
859
        $exceptionHandlerClassName = $this->determineExceptionHandlerClassName($configuration);
860
        if (!empty($exceptionHandlerClassName)) {
861
            $exceptionHandler = GeneralUtility::makeInstance($exceptionHandlerClassName, $this->mergeExceptionHandlerConfiguration($configuration));
862
            if (!$exceptionHandler instanceof ExceptionHandlerInterface) {
863
                throw new ContentRenderingException('An exception handler was configured but the class does not exist or does not implement the ExceptionHandlerInterface', 1403653369);
864
            }
865
        }
866
867
        return $exceptionHandler;
868
    }
869
870
    /**
871
     * Determine exception handler class name from global and content object configuration
872
     *
873
     * @param array $configuration
874
     * @return string|null
875
     */
876
    protected function determineExceptionHandlerClassName($configuration)
877
    {
878
        $exceptionHandlerClassName = null;
879
        $tsfe = $this->getTypoScriptFrontendController();
880
        if (!isset($tsfe->config['config']['contentObjectExceptionHandler'])) {
881
            if (Environment::getContext()->isProduction()) {
882
                $exceptionHandlerClassName = '1';
883
            }
884
        } else {
885
            $exceptionHandlerClassName = $tsfe->config['config']['contentObjectExceptionHandler'];
886
        }
887
888
        if (isset($configuration['exceptionHandler'])) {
889
            $exceptionHandlerClassName = $configuration['exceptionHandler'];
890
        }
891
892
        if ($exceptionHandlerClassName === '1') {
893
            $exceptionHandlerClassName = ProductionExceptionHandler::class;
894
        }
895
896
        return $exceptionHandlerClassName;
897
    }
898
899
    /**
900
     * Merges global exception handler configuration with the one from the content object
901
     * and returns the merged exception handler configuration
902
     *
903
     * @param array $configuration
904
     * @return array
905
     */
906
    protected function mergeExceptionHandlerConfiguration($configuration)
907
    {
908
        $exceptionHandlerConfiguration = [];
909
        $tsfe = $this->getTypoScriptFrontendController();
910
        if (!empty($tsfe->config['config']['contentObjectExceptionHandler.'])) {
911
            $exceptionHandlerConfiguration = $tsfe->config['config']['contentObjectExceptionHandler.'];
912
        }
913
        if (!empty($configuration['exceptionHandler.'])) {
914
            $exceptionHandlerConfiguration = array_replace_recursive($exceptionHandlerConfiguration, $configuration['exceptionHandler.']);
915
        }
916
917
        return $exceptionHandlerConfiguration;
918
    }
919
920
    /**
921
     * Retrieves a type of object called as USER or USER_INT. Object can detect their
922
     * type by using this call. It returns OBJECTTYPE_USER_INT or OBJECTTYPE_USER depending on the
923
     * current object execution. In all other cases it will return FALSE to indicate
924
     * a call out of context.
925
     *
926
     * @return mixed One of OBJECTTYPE_ class constants or FALSE
927
     */
928
    public function getUserObjectType()
929
    {
930
        return $this->userObjectType;
931
    }
932
933
    /**
934
     * Sets the user object type
935
     *
936
     * @param mixed $userObjectType
937
     */
938
    public function setUserObjectType($userObjectType)
939
    {
940
        $this->userObjectType = $userObjectType;
941
    }
942
943
    /**
944
     * Requests the current USER object to be converted to USER_INT.
945
     */
946
    public function convertToUserIntObject()
947
    {
948
        if ($this->userObjectType !== self::OBJECTTYPE_USER) {
0 ignored issues
show
introduced by
The condition $this->userObjectType !== self::OBJECTTYPE_USER is always true.
Loading history...
949
            $this->getTimeTracker()->setTSlogMessage(self::class . '::convertToUserIntObject() is called in the wrong context or for the wrong object type', 2);
950
        } else {
951
            $this->doConvertToUserIntObject = true;
952
        }
953
    }
954
955
    /************************************
956
     *
957
     * Various helper functions for content objects:
958
     *
959
     ************************************/
960
    /**
961
     * Converts a given config in Flexform to a conf-array
962
     *
963
     * @param string|array $flexData Flexform data
964
     * @param array $conf Array to write the data into, by reference
965
     * @param bool $recursive Is set if called recursive. Don't call function with this parameter, it's used inside the function only
966
     */
967
    public function readFlexformIntoConf($flexData, &$conf, $recursive = false)
968
    {
969
        if ($recursive === false && is_string($flexData)) {
970
            $flexData = GeneralUtility::xml2array($flexData, 'T3');
971
        }
972
        if (is_array($flexData) && isset($flexData['data']['sDEF']['lDEF'])) {
973
            $flexData = $flexData['data']['sDEF']['lDEF'];
974
        }
975
        if (!is_array($flexData)) {
976
            return;
977
        }
978
        foreach ($flexData as $key => $value) {
979
            if (!is_array($value)) {
980
                continue;
981
            }
982
            if (isset($value['el'])) {
983
                if (is_array($value['el']) && !empty($value['el'])) {
984
                    foreach ($value['el'] as $ekey => $element) {
985
                        if (isset($element['vDEF'])) {
986
                            $conf[$ekey] = $element['vDEF'];
987
                        } else {
988
                            if (is_array($element)) {
989
                                $this->readFlexformIntoConf($element, $conf[$key][key($element)][$ekey], true);
990
                            } else {
991
                                $this->readFlexformIntoConf($element, $conf[$key][$ekey], true);
992
                            }
993
                        }
994
                    }
995
                } else {
996
                    $this->readFlexformIntoConf($value['el'], $conf[$key], true);
997
                }
998
            }
999
            if (isset($value['vDEF'])) {
1000
                $conf[$key] = $value['vDEF'];
1001
            }
1002
        }
1003
    }
1004
1005
    /**
1006
     * Returns all parents of the given PID (Page UID) list
1007
     *
1008
     * @param string $pidList A list of page Content-Element PIDs (Page UIDs) / stdWrap
1009
     * @param array $pidConf stdWrap array for the list
1010
     * @return string A list of PIDs
1011
     * @internal
1012
     */
1013
    public function getSlidePids($pidList, $pidConf)
1014
    {
1015
        // todo: phpstan states that $pidConf always exists and is not nullable. At the moment, this is a false positive
1016
        //       as null can be passed into this method via $pidConf. As soon as more strict types are used, this isset
1017
        //       check must be replaced with a more appropriate check like empty or count.
1018
        $pidList = isset($pidConf) ? trim($this->stdWrap($pidList, $pidConf)) : trim($pidList);
1019
        if ($pidList === '') {
1020
            $pidList = 'this';
1021
        }
1022
        $tsfe = $this->getTypoScriptFrontendController();
1023
        $listArr = null;
1024
        if (trim($pidList)) {
1025
            $listArr = GeneralUtility::intExplode(',', str_replace('this', $tsfe->contentPid, $pidList));
1026
            $listArr = $this->checkPidArray($listArr);
1027
        }
1028
        $pidList = [];
1029
        if (is_array($listArr) && !empty($listArr)) {
1030
            foreach ($listArr as $uid) {
1031
                $page = $tsfe->sys_page->getPage($uid);
1032
                if (!$page['is_siteroot']) {
1033
                    $pidList[] = $page['pid'];
1034
                }
1035
            }
1036
        }
1037
        return implode(',', $pidList);
1038
    }
1039
1040
    /**
1041
     * Returns a <img> tag with the image file defined by $file and processed according to the properties in the TypoScript array.
1042
     * Mostly this function is a sub-function to the IMAGE function which renders the IMAGE cObject in TypoScript.
1043
     * This function is called by "$this->cImage($conf['file'], $conf);" from IMAGE().
1044
     *
1045
     * @param string $file File TypoScript resource
1046
     * @param array $conf TypoScript configuration properties
1047
     * @return string HTML <img> tag, (possibly wrapped in links and other HTML) if any image found.
1048
     * @internal
1049
     * @see IMAGE()
1050
     */
1051
    public function cImage($file, $conf)
1052
    {
1053
        $tsfe = $this->getTypoScriptFrontendController();
1054
        $info = $this->getImgResource($file, $conf['file.']);
1055
        $tsfe->lastImageInfo = $info;
0 ignored issues
show
Bug Best Practice introduced by
The property $lastImageInfo is declared private in TYPO3\CMS\Frontend\Contr...criptFrontendController. Since you implement __set, consider adding a @property or @property-write.
Loading history...
1056
        if (!is_array($info)) {
1057
            return '';
1058
        }
1059
        if (is_file(Environment::getPublicPath() . '/' . $info['3'])) {
1060
            $source = $tsfe->absRefPrefix . str_replace('%2F', '/', rawurlencode($info['3']));
1061
        } else {
1062
            $source = $info[3];
1063
        }
1064
        // Remove file objects for AssetCollector, as it only allows to store scalar values
1065
        unset($info['originalFile'], $info['processedFile']);
1066
        GeneralUtility::makeInstance(AssetCollector::class)->addMedia(
1067
            $source,
1068
            $info
1069
        );
1070
1071
        $layoutKey = $this->stdWrap($conf['layoutKey'], $conf['layoutKey.']);
1072
        $imageTagTemplate = $this->getImageTagTemplate($layoutKey, $conf);
1073
        $sourceCollection = $this->getImageSourceCollection($layoutKey, $conf, $file);
1074
1075
        // This array is used to collect the image-refs on the page...
1076
        $tsfe->imagesOnPage[] = $source;
0 ignored issues
show
Bug Best Practice introduced by
The property $imagesOnPage is declared private in TYPO3\CMS\Frontend\Contr...criptFrontendController. Since you implement __set, consider adding a @property or @property-write.
Loading history...
1077
        $altParam = $this->getAltParam($conf);
1078
        $params = $this->stdWrapValue('params', $conf);
1079
        if ($params !== '' && $params[0] !== ' ') {
1080
            $params = ' ' . $params;
1081
        }
1082
1083
        $imageTagValues = [
1084
            'width' =>  (int)$info[0],
1085
            'height' => (int)$info[1],
1086
            'src' => htmlspecialchars($source),
1087
            'params' => $params,
1088
            'altParams' => $altParam,
1089
            'border' =>  $this->getBorderAttr(' border="' . (int)$conf['border'] . '"'),
1090
            'sourceCollection' => $sourceCollection,
1091
            'selfClosingTagSlash' => !empty($tsfe->xhtmlDoctype) ? ' /' : '',
1092
        ];
1093
1094
        $markerTemplateEngine = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
1095
        $theValue = $markerTemplateEngine->substituteMarkerArray($imageTagTemplate, $imageTagValues, '###|###', true, true);
1096
1097
        $linkWrap = isset($conf['linkWrap.']) ? $this->stdWrap($conf['linkWrap'], $conf['linkWrap.']) : $conf['linkWrap'];
1098
        if ($linkWrap) {
1099
            $theValue = $this->linkWrap($theValue, $linkWrap);
1100
        } elseif ($conf['imageLinkWrap']) {
1101
            $originalFile = !empty($info['originalFile']) ? $info['originalFile'] : $info['origFile'];
1102
            $theValue = $this->imageLinkWrap($theValue, $originalFile, $conf['imageLinkWrap.']);
1103
        }
1104
        $wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
1105
        if ((string)$wrap !== '') {
1106
            $theValue = $this->wrap($theValue, $conf['wrap']);
1107
        }
1108
        return $theValue;
1109
    }
1110
1111
    /**
1112
     * Returns the 'border' attribute for an <img> tag only if the doctype is not xhtml_strict, xhtml_11 or html5
1113
     * or if the config parameter 'disableImgBorderAttr' is not set.
1114
     *
1115
     * @param string $borderAttr The border attribute
1116
     * @return string The border attribute
1117
     */
1118
    public function getBorderAttr($borderAttr)
1119
    {
1120
        $tsfe = $this->getTypoScriptFrontendController();
1121
        $docType = $tsfe->xhtmlDoctype;
1122
        if (
1123
            $docType !== 'xhtml_strict' && $docType !== 'xhtml_11'
1124
            && $tsfe->config['config']['doctype'] !== 'html5'
1125
            && !$tsfe->config['config']['disableImgBorderAttr']
1126
        ) {
1127
            return $borderAttr;
1128
        }
1129
        return '';
1130
    }
1131
1132
    /**
1133
     * Returns the html-template for rendering the image-Tag if no template is defined via typoscript the
1134
     * default <img> tag template is returned
1135
     *
1136
     * @param string $layoutKey rendering key
1137
     * @param array $conf TypoScript configuration properties
1138
     * @return string
1139
     */
1140
    public function getImageTagTemplate($layoutKey, $conf)
1141
    {
1142
        if ($layoutKey && isset($conf['layout.']) && isset($conf['layout.'][$layoutKey . '.'])) {
1143
            $imageTagLayout = $this->stdWrap(
1144
                $conf['layout.'][$layoutKey . '.']['element'] ?? '',
1145
                $conf['layout.'][$layoutKey . '.']['element.'] ?? []
1146
            );
1147
        } else {
1148
            $imageTagLayout = '<img src="###SRC###" width="###WIDTH###" height="###HEIGHT###" ###PARAMS### ###ALTPARAMS### ###BORDER######SELFCLOSINGTAGSLASH###>';
1149
        }
1150
        return $imageTagLayout;
1151
    }
1152
1153
    /**
1154
     * Render alternate sources for the image tag. If no source collection is given an empty string is returned.
1155
     *
1156
     * @param string $layoutKey rendering key
1157
     * @param array $conf TypoScript configuration properties
1158
     * @param string $file
1159
     * @throws \UnexpectedValueException
1160
     * @return string
1161
     */
1162
    public function getImageSourceCollection($layoutKey, $conf, $file)
1163
    {
1164
        $sourceCollection = '';
1165
        if ($layoutKey
1166
            && isset($conf['sourceCollection.']) && $conf['sourceCollection.']
1167
            && (
1168
                isset($conf['layout.'][$layoutKey . '.']['source']) && $conf['layout.'][$layoutKey . '.']['source']
1169
                || isset($conf['layout.'][$layoutKey . '.']['source.']) && $conf['layout.'][$layoutKey . '.']['source.']
1170
            )
1171
        ) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
1172
1173
            // find active sourceCollection
1174
            $activeSourceCollections = [];
1175
            foreach ($conf['sourceCollection.'] as $sourceCollectionKey => $sourceCollectionConfiguration) {
1176
                if (substr($sourceCollectionKey, -1) === '.') {
1177
                    if (empty($sourceCollectionConfiguration['if.']) || $this->checkIf($sourceCollectionConfiguration['if.'])) {
1178
                        $activeSourceCollections[] = $sourceCollectionConfiguration;
1179
                    }
1180
                }
1181
            }
1182
1183
            // apply option split to configurations
1184
            $tsfe = $this->getTypoScriptFrontendController();
1185
            $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
1186
            $srcLayoutOptionSplitted = $typoScriptService->explodeConfigurationForOptionSplit((array)$conf['layout.'][$layoutKey . '.'], count($activeSourceCollections));
1187
1188
            // render sources
1189
            foreach ($activeSourceCollections as $key => $sourceConfiguration) {
1190
                $sourceLayout = $this->stdWrap(
1191
                    $srcLayoutOptionSplitted[$key]['source'] ?? '',
1192
                    $srcLayoutOptionSplitted[$key]['source.'] ?? []
1193
                );
1194
1195
                $sourceRenderConfiguration = [
1196
                    'file' => $file,
1197
                    'file.' => $conf['file.'] ?? null
1198
                ];
1199
1200
                if (isset($sourceConfiguration['quality']) || isset($sourceConfiguration['quality.'])) {
1201
                    $imageQuality = $sourceConfiguration['quality'] ?? '';
1202
                    if (isset($sourceConfiguration['quality.'])) {
1203
                        $imageQuality = $this->stdWrap($sourceConfiguration['quality'], $sourceConfiguration['quality.']);
1204
                    }
1205
                    if ($imageQuality) {
1206
                        $sourceRenderConfiguration['file.']['params'] = '-quality ' . (int)$imageQuality;
1207
                    }
1208
                }
1209
1210
                if (isset($sourceConfiguration['pixelDensity'])) {
1211
                    $pixelDensity = (int)$this->stdWrap(
1212
                        $sourceConfiguration['pixelDensity'] ?? '',
1213
                        $sourceConfiguration['pixelDensity.'] ?? []
1214
                    );
1215
                } else {
1216
                    $pixelDensity = 1;
1217
                }
1218
                $dimensionKeys = ['width', 'height', 'maxW', 'minW', 'maxH', 'minH', 'maxWidth', 'maxHeight', 'XY'];
1219
                foreach ($dimensionKeys as $dimensionKey) {
1220
                    $dimension = $this->stdWrap(
1221
                        $sourceConfiguration[$dimensionKey] ?? '',
1222
                        $sourceConfiguration[$dimensionKey . '.'] ?? []
1223
                    );
1224
                    if (!$dimension) {
1225
                        $dimension = $this->stdWrap(
1226
                            $conf['file.'][$dimensionKey] ?? '',
1227
                            $conf['file.'][$dimensionKey . '.'] ?? []
1228
                        );
1229
                    }
1230
                    if ($dimension) {
1231
                        if (strpos($dimension, 'c') !== false && ($dimensionKey === 'width' || $dimensionKey === 'height')) {
1232
                            $dimensionParts = explode('c', $dimension, 2);
1233
                            $dimension = ((int)$dimensionParts[0] * $pixelDensity) . 'c';
1234
                            if ($dimensionParts[1]) {
1235
                                $dimension .= $dimensionParts[1];
1236
                            }
1237
                        } elseif ($dimensionKey === 'XY') {
1238
                            $dimensionParts = GeneralUtility::intExplode(',', $dimension, false, 2);
1239
                            $dimension = $dimensionParts[0] * $pixelDensity;
1240
                            if ($dimensionParts[1]) {
1241
                                $dimension .= ',' . $dimensionParts[1] * $pixelDensity;
1242
                            }
1243
                        } else {
1244
                            $dimension = (int)$dimension * $pixelDensity;
1245
                        }
1246
                        $sourceRenderConfiguration['file.'][$dimensionKey] = $dimension;
1247
                        // Remove the stdWrap properties for dimension as they have been processed already above.
1248
                        unset($sourceRenderConfiguration['file.'][$dimensionKey . '.']);
1249
                    }
1250
                }
1251
                $sourceInfo = $this->getImgResource($sourceRenderConfiguration['file'], $sourceRenderConfiguration['file.']);
1252
                if ($sourceInfo) {
1253
                    $sourceConfiguration['width'] = $sourceInfo[0];
1254
                    $sourceConfiguration['height'] = $sourceInfo[1];
1255
                    $urlPrefix = '';
1256
                    if (parse_url($sourceInfo[3], PHP_URL_HOST) === null) {
1257
                        $urlPrefix = $tsfe->absRefPrefix;
1258
                    }
1259
                    $sourceConfiguration['src'] = htmlspecialchars($urlPrefix . $sourceInfo[3]);
1260
                    $sourceConfiguration['selfClosingTagSlash'] = !empty($tsfe->xhtmlDoctype) ? ' /' : '';
1261
1262
                    $markerTemplateEngine = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
1263
                    $oneSourceCollection = $markerTemplateEngine->substituteMarkerArray($sourceLayout, $sourceConfiguration, '###|###', true, true);
1264
1265
                    foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImageSourceCollection'] ?? [] as $className) {
1266
                        $hookObject = GeneralUtility::makeInstance($className);
1267
                        if (!$hookObject instanceof ContentObjectOneSourceCollectionHookInterface) {
1268
                            throw new \UnexpectedValueException(
1269
                                '$hookObject must implement interface ' . ContentObjectOneSourceCollectionHookInterface::class,
1270
                                1380007853
1271
                            );
1272
                        }
1273
                        $oneSourceCollection = $hookObject->getOneSourceCollection((array)$sourceRenderConfiguration, (array)$sourceConfiguration, $oneSourceCollection, $this);
1274
                    }
1275
1276
                    $sourceCollection .= $oneSourceCollection;
1277
                }
1278
            }
1279
        }
1280
        return $sourceCollection;
1281
    }
1282
1283
    /**
1284
     * Wraps the input string in link-tags that opens the image in a new window.
1285
     *
1286
     * @param string $string String to wrap, probably an <img> tag
1287
     * @param string|File|FileReference $imageFile The original image file
1288
     * @param array $conf TypoScript properties for the "imageLinkWrap" function
1289
     * @return string The input string, $string, wrapped as configured.
1290
     * @see cImage()
1291
     */
1292
    public function imageLinkWrap($string, $imageFile, $conf)
1293
    {
1294
        $string = (string)$string;
1295
        $enable = isset($conf['enable.']) ? $this->stdWrap($conf['enable'], $conf['enable.']) : $conf['enable'];
1296
        if (!$enable) {
1297
            return $string;
1298
        }
1299
        $content = (string)$this->typoLink($string, $conf['typolink.']);
1300
        if (isset($conf['file.'])) {
1301
            $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\File and TYPO3\CMS\Core\Resource\FileReference; 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

1301
            $imageFile = $this->stdWrap(/** @scrutinizer ignore-type */ $imageFile, $conf['file.']);
Loading history...
1302
        }
1303
1304
        if ($imageFile instanceof File) {
1305
            $file = $imageFile;
1306
        } elseif ($imageFile instanceof FileReference) {
1307
            $file = $imageFile->getOriginalFile();
1308
        } else {
1309
            if (MathUtility::canBeInterpretedAsInteger($imageFile)) {
1310
                $file = GeneralUtility::makeInstance(ResourceFactory::class)->getFileObject((int)$imageFile);
1311
            } else {
1312
                $file = GeneralUtility::makeInstance(ResourceFactory::class)->getFileObjectFromCombinedIdentifier($imageFile);
1313
            }
1314
        }
1315
1316
        // Create imageFileLink if not created with typolink
1317
        if ($content === $string) {
1318
            $parameterNames = ['width', 'height', 'effects', 'bodyTag', 'title', 'wrap', 'crop'];
1319
            $parameters = [];
1320
            $sample = isset($conf['sample.']) ? $this->stdWrap($conf['sample'], $conf['sample.']) : $conf['sample'];
1321
            if ($sample) {
1322
                $parameters['sample'] = 1;
1323
            }
1324
            foreach ($parameterNames as $parameterName) {
1325
                if (isset($conf[$parameterName . '.'])) {
1326
                    $conf[$parameterName] = $this->stdWrap($conf[$parameterName], $conf[$parameterName . '.']);
1327
                }
1328
                if (isset($conf[$parameterName]) && $conf[$parameterName]) {
1329
                    $parameters[$parameterName] = $conf[$parameterName];
1330
                }
1331
            }
1332
            $parametersEncoded = base64_encode(serialize($parameters));
1333
            $hmac = GeneralUtility::hmac(implode('|', [$file->getUid(), $parametersEncoded]));
1334
            $params = '&md5=' . $hmac;
1335
            foreach (str_split($parametersEncoded, 64) as $index => $chunk) {
1336
                $params .= '&parameters' . rawurlencode('[') . $index . rawurlencode(']') . '=' . rawurlencode($chunk);
1337
            }
1338
            $url = $this->getTypoScriptFrontendController()->absRefPrefix . 'index.php?eID=tx_cms_showpic&file=' . $file->getUid() . $params;
1339
            $directImageLink = isset($conf['directImageLink.']) ? $this->stdWrap($conf['directImageLink'], $conf['directImageLink.']) : $conf['directImageLink'];
1340
            if ($directImageLink) {
1341
                $imgResourceConf = [
1342
                    'file' => $imageFile,
1343
                    'file.' => $conf
1344
                ];
1345
                $url = $this->cObjGetSingle('IMG_RESOURCE', $imgResourceConf);
1346
                if (!$url) {
1347
                    // If no imagemagick / gm is available
1348
                    $url = $imageFile;
1349
                }
1350
            }
1351
            // Create TARGET-attribute only if the right doctype is used
1352
            $target = '';
1353
            $xhtmlDocType = $this->getTypoScriptFrontendController()->xhtmlDoctype;
1354
            if ($xhtmlDocType !== 'xhtml_strict' && $xhtmlDocType !== 'xhtml_11') {
1355
                $target = isset($conf['target.'])
1356
                    ? (string)$this->stdWrap($conf['target'], $conf['target.'])
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
1357
                    : (string)$conf['target'];
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
1358
                if ($target === '') {
1359
                    $target = 'thePicture';
1360
                }
1361
            }
1362
            $a1 = '';
1363
            $a2 = '';
1364
            $conf['JSwindow'] = isset($conf['JSwindow.']) ? $this->stdWrap($conf['JSwindow'], $conf['JSwindow.']) : $conf['JSwindow'];
1365
            if ($conf['JSwindow']) {
1366
                if ($conf['JSwindow.']['altUrl'] || $conf['JSwindow.']['altUrl.']) {
1367
                    $altUrl = isset($conf['JSwindow.']['altUrl.']) ? $this->stdWrap($conf['JSwindow.']['altUrl'], $conf['JSwindow.']['altUrl.']) : $conf['JSwindow.']['altUrl'];
1368
                    if ($altUrl) {
1369
                        $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\File and TYPO3\CMS\Core\Resource\FileReference; 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

1369
                        $url = $altUrl . ($conf['JSwindow.']['altUrl_noDefaultParams'] ? '' : '?file=' . rawurlencode(/** @scrutinizer ignore-type */ $imageFile) . $params);
Loading history...
1370
                    }
1371
                }
1372
1373
                $processedFile = $file->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $conf);
1374
                $JSwindowExpand = isset($conf['JSwindow.']['expand.']) ? $this->stdWrap($conf['JSwindow.']['expand'], $conf['JSwindow.']['expand.']) : $conf['JSwindow.']['expand'];
1375
                $offset = GeneralUtility::intExplode(',', $JSwindowExpand . ',');
1376
                $newWindow = isset($conf['JSwindow.']['newWindow.']) ? $this->stdWrap($conf['JSwindow.']['newWindow'], $conf['JSwindow.']['newWindow.']) : $conf['JSwindow.']['newWindow'];
1377
                $onClick = 'openPic('
1378
                    . 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\File and TYPO3\CMS\Core\Resource\FileReference; 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

1378
                    . GeneralUtility::quoteJSvalue($this->getTypoScriptFrontendController()->baseUrlWrap(/** @scrutinizer ignore-type */ $url)) . ','
Loading history...
1379
                    . '\'' . ($newWindow ? md5($url) : 'thePicture') . '\','
0 ignored issues
show
Bug introduced by
It seems like $url can also be of type TYPO3\CMS\Core\Resource\File and TYPO3\CMS\Core\Resource\FileReference; 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

1379
                    . '\'' . ($newWindow ? md5(/** @scrutinizer ignore-type */ $url) : 'thePicture') . '\','
Loading history...
1380
                    . GeneralUtility::quoteJSvalue('width=' . ($processedFile->getProperty('width') + $offset[0])
1381
                        . ',height=' . ($processedFile->getProperty('height') + $offset[1]) . ',status=0,menubar=0')
1382
                    . '); return false;';
1383
                $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\File and TYPO3\CMS\Core\Resource\FileReference; 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

1383
                $a1 = '<a href="' . htmlspecialchars(/** @scrutinizer ignore-type */ $url) . '"'
Loading history...
1384
                    . ' onclick="' . htmlspecialchars($onClick) . '"'
1385
                    . ($target !== '' ? ' target="' . htmlspecialchars($target) . '"' : '')
1386
                    . $this->getTypoScriptFrontendController()->ATagParams . '>';
1387
                $a2 = '</a>';
1388
                $this->getTypoScriptFrontendController()->setJS('openPic');
1389
            } else {
1390
                $conf['linkParams.']['directImageLink'] = (bool)$conf['directImageLink'];
1391
                $conf['linkParams.']['parameter'] = $url;
1392
                $string = $this->typoLink($string, $conf['linkParams.']);
1393
            }
1394
            if (isset($conf['stdWrap.'])) {
1395
                $string = $this->stdWrap($string, $conf['stdWrap.']);
1396
            }
1397
            $content = $a1 . $string . $a2;
1398
        }
1399
        return $content;
1400
    }
1401
1402
    /**
1403
     * Sets the SYS_LASTCHANGED timestamp if input timestamp is larger than current value.
1404
     * The SYS_LASTCHANGED timestamp can be used by various caching/indexing applications to determine if the page has new content.
1405
     * Therefore you should call this function with the last-changed timestamp of any element you display.
1406
     *
1407
     * @param int $tstamp Unix timestamp (number of seconds since 1970)
1408
     * @see TypoScriptFrontendController::setSysLastChanged()
1409
     */
1410
    public function lastChanged($tstamp)
1411
    {
1412
        $tstamp = (int)$tstamp;
1413
        $tsfe = $this->getTypoScriptFrontendController();
1414
        if ($tstamp > (int)$tsfe->register['SYS_LASTCHANGED']) {
1415
            $tsfe->register['SYS_LASTCHANGED'] = $tstamp;
1416
        }
1417
    }
1418
1419
    /**
1420
     * Wraps the input string by the $wrap value and implements the "linkWrap" data type as well.
1421
     * 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.
1422
     *
1423
     * @param string $content Input string
1424
     * @param string $wrap A string where the first two parts separated by "|" (vertical line) will be wrapped around the input string
1425
     * @return string Wrapped output string
1426
     * @see wrap()
1427
     * @see cImage()
1428
     */
1429
    public function linkWrap($content, $wrap)
1430
    {
1431
        $wrapArr = explode('|', $wrap);
1432
        if (preg_match('/\\{([0-9]*)\\}/', $wrapArr[0], $reg)) {
1433
            $uid = $this->getTypoScriptFrontendController()->tmpl->rootLine[$reg[1]]['uid'] ?? null;
1434
            if ($uid) {
1435
                $wrapArr[0] = str_replace($reg[0], $uid, $wrapArr[0]);
1436
            }
1437
        }
1438
        return trim($wrapArr[0] ?? '') . $content . trim($wrapArr[1] ?? '');
1439
    }
1440
1441
    /**
1442
     * An abstraction method which creates an alt or title parameter for an HTML img, applet, area or input element and the FILE content element.
1443
     * From the $conf array it implements the properties "altText", "titleText" and "longdescURL"
1444
     *
1445
     * @param array $conf TypoScript configuration properties
1446
     * @param bool $longDesc If set, the longdesc attribute will be generated - must only be used for img elements!
1447
     * @return string Parameter string containing alt and title parameters (if any)
1448
     * @see cImage()
1449
     */
1450
    public function getAltParam($conf, $longDesc = true)
1451
    {
1452
        $altText = isset($conf['altText.']) ? trim($this->stdWrap($conf['altText'], $conf['altText.'])) : trim($conf['altText']);
1453
        $titleText = isset($conf['titleText.']) ? trim($this->stdWrap($conf['titleText'], $conf['titleText.'])) : trim($conf['titleText']);
1454
        if (isset($conf['longdescURL.']) && $this->getTypoScriptFrontendController()->config['config']['doctype'] !== 'html5') {
1455
            $longDescUrl = $this->typoLink_URL($conf['longdescURL.']);
1456
        } else {
1457
            $longDescUrl = trim($conf['longdescURL']);
1458
        }
1459
        $longDescUrl = strip_tags($longDescUrl);
1460
1461
        // "alt":
1462
        $altParam = ' alt="' . htmlspecialchars($altText) . '"';
1463
        // "title":
1464
        $emptyTitleHandling = isset($conf['emptyTitleHandling.']) ? $this->stdWrap($conf['emptyTitleHandling'], $conf['emptyTitleHandling.']) : $conf['emptyTitleHandling'];
1465
        // Choices: 'keepEmpty' | 'useAlt' | 'removeAttr'
1466
        if ($titleText || $emptyTitleHandling === 'keepEmpty') {
1467
            $altParam .= ' title="' . htmlspecialchars($titleText) . '"';
1468
        } elseif (!$titleText && $emptyTitleHandling === 'useAlt') {
1469
            $altParam .= ' title="' . htmlspecialchars($altText) . '"';
1470
        }
1471
        // "longDesc" URL
1472
        if ($longDesc && !empty($longDescUrl)) {
1473
            $altParam .= ' longdesc="' . htmlspecialchars($longDescUrl) . '"';
1474
        }
1475
        return $altParam;
1476
    }
1477
1478
    /**
1479
     * An abstraction method to add parameters to an A tag.
1480
     * Uses the ATagParams property.
1481
     *
1482
     * @param array $conf TypoScript configuration properties
1483
     * @param bool|int $addGlobal If set, will add the global config.ATagParams to the link
1484
     * @return string String containing the parameters to the A tag (if non empty, with a leading space)
1485
     * @see typolink()
1486
     */
1487
    public function getATagParams($conf, $addGlobal = 1)
1488
    {
1489
        $aTagParams = '';
1490
        if ($conf['ATagParams.'] ?? false) {
1491
            $aTagParams = ' ' . $this->stdWrap($conf['ATagParams'], $conf['ATagParams.']);
1492
        } elseif ($conf['ATagParams'] ?? false) {
1493
            $aTagParams = ' ' . $conf['ATagParams'];
1494
        }
1495
        if ($addGlobal) {
1496
            $aTagParams = ' ' . trim($this->getTypoScriptFrontendController()->ATagParams . $aTagParams);
1497
        }
1498
        // Extend params
1499
        $_params = [
1500
            'conf' => &$conf,
1501
            'aTagParams' => &$aTagParams
1502
        ];
1503
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getATagParamsPostProc'] ?? [] as $className) {
1504
            $processor = & GeneralUtility::makeInstance($className);
1505
            $aTagParams = $processor->process($_params, $this);
1506
        }
1507
1508
        $aTagParams = trim($aTagParams);
1509
        if (!empty($aTagParams)) {
1510
            $aTagParams = ' ' . $aTagParams;
1511
        }
1512
1513
        return $aTagParams;
1514
    }
1515
1516
    /**
1517
     * All extension links should ask this function for additional properties to their tags.
1518
     * Designed to add for instance an "onclick" property for site tracking systems.
1519
     *
1520
     * @param string $URL URL of the website
1521
     * @param string $TYPE
1522
     * @return string The additional tag properties
1523
     */
1524
    public function extLinkATagParams($URL, $TYPE)
1525
    {
1526
        $out = '';
1527
        if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['extLinkATagParamsHandler'])) {
1528
            $extLinkATagParamsHandler = GeneralUtility::makeInstance($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['extLinkATagParamsHandler']);
1529
            if (method_exists($extLinkATagParamsHandler, 'main')) {
1530
                $out .= trim($extLinkATagParamsHandler->main($URL, $TYPE, $this));
1531
            }
1532
        }
1533
        return trim($out) ? ' ' . trim($out) : '';
1534
    }
1535
1536
    /***********************************************
1537
     *
1538
     * HTML template processing functions
1539
     *
1540
     ***********************************************/
1541
1542
    /**
1543
     * Sets the current file object during iterations over files.
1544
     *
1545
     * @param File $fileObject The file object.
1546
     */
1547
    public function setCurrentFile($fileObject)
1548
    {
1549
        $this->currentFile = $fileObject;
1550
    }
1551
1552
    /**
1553
     * Gets the current file object during iterations over files.
1554
     *
1555
     * @return File The current file object.
1556
     */
1557
    public function getCurrentFile()
1558
    {
1559
        return $this->currentFile;
1560
    }
1561
1562
    /***********************************************
1563
     *
1564
     * "stdWrap" + sub functions
1565
     *
1566
     ***********************************************/
1567
    /**
1568
     * The "stdWrap" function. This is the implementation of what is known as "stdWrap properties" in TypoScript.
1569
     * Basically "stdWrap" performs some processing of a value based on properties in the input $conf array(holding the TypoScript "stdWrap properties")
1570
     * 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.
1571
     *
1572
     * If $this->alternativeData is an array it's used instead of the $this->data array in ->getData
1573
     *
1574
     * @param string $content Input value undergoing processing in this function. Possibly substituted by other values fetched from another source.
1575
     * @param array $conf TypoScript "stdWrap properties".
1576
     * @return string The processed input value
1577
     */
1578
    public function stdWrap($content = '', $conf = [])
1579
    {
1580
        $content = (string)$content;
1581
        // If there is any hook object, activate all of the process and override functions.
1582
        // The hook interface ContentObjectStdWrapHookInterface takes care that all 4 methods exist.
1583
        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...
1584
            $conf['stdWrapPreProcess'] = 1;
1585
            $conf['stdWrapOverride'] = 1;
1586
            $conf['stdWrapProcess'] = 1;
1587
            $conf['stdWrapPostProcess'] = 1;
1588
        }
1589
1590
        if (!is_array($conf) || !$conf) {
0 ignored issues
show
introduced by
The condition is_array($conf) is always true.
Loading history...
1591
            return $content;
1592
        }
1593
1594
        // Cache handling
1595
        if (isset($conf['cache.']) && is_array($conf['cache.'])) {
1596
            $conf['cache.']['key'] = $this->stdWrap($conf['cache.']['key'], $conf['cache.']['key.']);
1597
            $conf['cache.']['tags'] = $this->stdWrap($conf['cache.']['tags'], $conf['cache.']['tags.']);
1598
            $conf['cache.']['lifetime'] = $this->stdWrap($conf['cache.']['lifetime'], $conf['cache.']['lifetime.']);
1599
            $conf['cacheRead'] = 1;
1600
            $conf['cacheStore'] = 1;
1601
        }
1602
        // The configuration is sorted and filtered by intersection with the defined stdWrapOrder.
1603
        $sortedConf = array_keys(array_intersect_key($this->stdWrapOrder, $conf));
1604
        // Functions types that should not make use of nested stdWrap function calls to avoid conflicts with internal TypoScript used by these functions
1605
        $stdWrapDisabledFunctionTypes = 'cObject,functionName,stdWrap';
1606
        // Additional Array to check whether a function has already been executed
1607
        $isExecuted = [];
1608
        // Additional switch to make sure 'required', 'if' and 'fieldRequired'
1609
        // will still stop rendering immediately in case they return FALSE
1610
        $this->stdWrapRecursionLevel++;
1611
        $this->stopRendering[$this->stdWrapRecursionLevel] = false;
1612
        // execute each function in the predefined order
1613
        foreach ($sortedConf as $stdWrapName) {
1614
            // eliminate the second key of a pair 'key'|'key.' to make sure functions get called only once and check if rendering has been stopped
1615
            if ((!isset($isExecuted[$stdWrapName]) || !$isExecuted[$stdWrapName]) && !$this->stopRendering[$this->stdWrapRecursionLevel]) {
1616
                $functionName = rtrim($stdWrapName, '.');
1617
                $functionProperties = $functionName . '.';
1618
                $functionType = $this->stdWrapOrder[$functionName] ?? null;
1619
                // If there is any code on the next level, check if it contains "official" stdWrap functions
1620
                // if yes, execute them first - will make each function stdWrap aware
1621
                // so additional stdWrap calls within the functions can be removed, since the result will be the same
1622
                if (!empty($conf[$functionProperties]) && !GeneralUtility::inList($stdWrapDisabledFunctionTypes, $functionType)) {
1623
                    if (array_intersect_key($this->stdWrapOrder, $conf[$functionProperties])) {
1624
                        // Check if there's already content available before processing
1625
                        // any ifEmpty or ifBlank stdWrap properties
1626
                        if (($functionName === 'ifBlank' && $content !== '') ||
1627
                            ($functionName === 'ifEmpty' && trim($content) !== '')) {
1628
                            continue;
1629
                        }
1630
1631
                        $conf[$functionName] = $this->stdWrap($conf[$functionName] ?? '', $conf[$functionProperties] ?? []);
1632
                    }
1633
                }
1634
                // Check if key is still containing something, since it might have been changed by next level stdWrap before
1635
                if ((isset($conf[$functionName]) || $conf[$functionProperties])
1636
                    && ($functionType !== 'boolean' || $conf[$functionName])
1637
                ) {
1638
                    // Get just that part of $conf that is needed for the particular function
1639
                    $singleConf = [
1640
                        $functionName => $conf[$functionName] ?? null,
1641
                        $functionProperties => $conf[$functionProperties] ?? null
1642
                    ];
1643
                    // Hand over the whole $conf array to the stdWrapHookObjects
1644
                    if ($functionType === 'hook') {
1645
                        $singleConf = $conf;
1646
                    }
1647
                    // Add both keys - with and without the dot - to the set of executed functions
1648
                    $isExecuted[$functionName] = true;
1649
                    $isExecuted[$functionProperties] = true;
1650
                    // Call the function with the prefix stdWrap_ to make sure nobody can execute functions just by adding their name to the TS Array
1651
                    $functionName = 'stdWrap_' . $functionName;
1652
                    $content = $this->{$functionName}($content, $singleConf);
1653
                } elseif ($functionType === 'boolean' && !$conf[$functionName]) {
1654
                    $isExecuted[$functionName] = true;
1655
                    $isExecuted[$functionProperties] = true;
1656
                }
1657
            }
1658
        }
1659
        unset($this->stopRendering[$this->stdWrapRecursionLevel]);
1660
        $this->stdWrapRecursionLevel--;
1661
1662
        return $content;
1663
    }
1664
1665
    /**
1666
     * Gets a configuration value by passing them through stdWrap first and taking a default value if stdWrap doesn't yield a result.
1667
     *
1668
     * @param string $key The config variable key (from TS array).
1669
     * @param array $config The TypoScript array.
1670
     * @param string $defaultValue Optional default value.
1671
     * @return string Value of the config variable
1672
     */
1673
    public function stdWrapValue($key, array $config, $defaultValue = '')
1674
    {
1675
        if (isset($config[$key])) {
1676
            if (!isset($config[$key . '.'])) {
1677
                return $config[$key];
1678
            }
1679
        } elseif (isset($config[$key . '.'])) {
1680
            $config[$key] = '';
1681
        } else {
1682
            return $defaultValue;
1683
        }
1684
        $stdWrapped = $this->stdWrap($config[$key], $config[$key . '.']);
1685
        return $stdWrapped ?: $defaultValue;
1686
    }
1687
1688
    /**
1689
     * stdWrap pre process hook
1690
     * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
1691
     * this hook will execute functions before any other stdWrap function can modify anything
1692
     *
1693
     * @param string $content Input value undergoing processing in these functions.
1694
     * @param array $conf All stdWrap properties, not just the ones for a particular function.
1695
     * @return string The processed input value
1696
     */
1697
    public function stdWrap_stdWrapPreProcess($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_stdWrapPreProcess" is not in camel caps format
Loading history...
1698
    {
1699
        foreach ($this->stdWrapHookObjects as $hookObject) {
1700
            /** @var ContentObjectStdWrapHookInterface $hookObject */
1701
            $content = $hookObject->stdWrapPreProcess($content, $conf, $this);
1702
        }
1703
        return $content;
1704
    }
1705
1706
    /**
1707
     * Check if content was cached before (depending on the given cache key)
1708
     *
1709
     * @param string $content Input value undergoing processing in these functions.
1710
     * @param array $conf All stdWrap properties, not just the ones for a particular function.
1711
     * @return string The processed input value
1712
     */
1713
    public function stdWrap_cacheRead($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_cacheRead" is not in camel caps format
Loading history...
1714
    {
1715
        if (!isset($conf['cache.'])) {
1716
            return $content;
1717
        }
1718
        $result = $this->getFromCache($conf['cache.']);
1719
        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...
1720
    }
1721
1722
    /**
1723
     * Add tags to page cache (comma-separated list)
1724
     *
1725
     * @param string $content Input value undergoing processing in these functions.
1726
     * @param array $conf All stdWrap properties, not just the ones for a particular function.
1727
     * @return string The processed input value
1728
     */
1729
    public function stdWrap_addPageCacheTags($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_addPageCacheTags" is not in camel caps format
Loading history...
1730
    {
1731
        $tags = isset($conf['addPageCacheTags.'])
1732
            ? $this->stdWrap($conf['addPageCacheTags'], $conf['addPageCacheTags.'])
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
1733
            : $conf['addPageCacheTags'];
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
1734
        if (!empty($tags)) {
1735
            $cacheTags = GeneralUtility::trimExplode(',', $tags, true);
1736
            $this->getTypoScriptFrontendController()->addCacheTags($cacheTags);
1737
        }
1738
        return $content;
1739
    }
1740
1741
    /**
1742
     * setContentToCurrent
1743
     * actually it just does the contrary: Sets the value of 'current' based on current content
1744
     *
1745
     * @param string $content Input value undergoing processing in this function.
1746
     * @return string The processed input value
1747
     */
1748
    public function stdWrap_setContentToCurrent($content = '')
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_setContentToCurrent" is not in camel caps format
Loading history...
1749
    {
1750
        $this->data[$this->currentValKey] = $content;
1751
        return $content;
1752
    }
1753
1754
    /**
1755
     * setCurrent
1756
     * Sets the value of 'current' based on the outcome of stdWrap operations
1757
     *
1758
     * @param string $content Input value undergoing processing in this function.
1759
     * @param array $conf stdWrap properties for setCurrent.
1760
     * @return string The processed input value
1761
     */
1762
    public function stdWrap_setCurrent($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_setCurrent" is not in camel caps format
Loading history...
1763
    {
1764
        $this->data[$this->currentValKey] = $conf['setCurrent'] ?? null;
1765
        return $content;
1766
    }
1767
1768
    /**
1769
     * lang
1770
     * Translates content based on the language currently used by the FE
1771
     *
1772
     * @param string $content Input value undergoing processing in this function.
1773
     * @param array $conf stdWrap properties for lang.
1774
     * @return string The processed input value
1775
     */
1776
    public function stdWrap_lang($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_lang" is not in camel caps format
Loading history...
1777
    {
1778
        $currentLanguageCode = $this->getTypoScriptFrontendController()->getLanguage()->getTypo3Language();
1779
        if ($currentLanguageCode && isset($conf['lang.'][$currentLanguageCode])) {
1780
            $content = $conf['lang.'][$currentLanguageCode];
1781
        }
1782
        return $content;
1783
    }
1784
1785
    /**
1786
     * data
1787
     * Gets content from different sources based on getText functions, makes use of alternativeData, when set
1788
     *
1789
     * @param string $content Input value undergoing processing in this function.
1790
     * @param array $conf stdWrap properties for data.
1791
     * @return string The processed input value
1792
     */
1793
    public function stdWrap_data($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_data" is not in camel caps format
Loading history...
1794
    {
1795
        $content = $this->getData($conf['data'], is_array($this->alternativeData) ? $this->alternativeData : $this->data);
0 ignored issues
show
introduced by
The condition is_array($this->alternativeData) is always false.
Loading history...
1796
        // This must be unset directly after
1797
        $this->alternativeData = '';
1798
        return $content;
1799
    }
1800
1801
    /**
1802
     * field
1803
     * Gets content from a DB field
1804
     *
1805
     * @param string $content Input value undergoing processing in this function.
1806
     * @param array $conf stdWrap properties for field.
1807
     * @return string The processed input value
1808
     */
1809
    public function stdWrap_field($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_field" is not in camel caps format
Loading history...
1810
    {
1811
        return $this->getFieldVal($conf['field']);
1812
    }
1813
1814
    /**
1815
     * current
1816
     * Gets content that has been previously set as 'current'
1817
     * Can be set via setContentToCurrent or setCurrent or will be set automatically i.e. inside the split function
1818
     *
1819
     * @param string $content Input value undergoing processing in this function.
1820
     * @param array $conf stdWrap properties for current.
1821
     * @return string The processed input value
1822
     */
1823
    public function stdWrap_current($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_current" is not in camel caps format
Loading history...
1824
    {
1825
        return $this->data[$this->currentValKey];
1826
    }
1827
1828
    /**
1829
     * cObject
1830
     * Will replace the content with the value of an official TypoScript cObject
1831
     * like TEXT, COA, HMENU
1832
     *
1833
     * @param string $content Input value undergoing processing in this function.
1834
     * @param array $conf stdWrap properties for cObject.
1835
     * @return string The processed input value
1836
     */
1837
    public function stdWrap_cObject($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_cObject" is not in camel caps format
Loading history...
1838
    {
1839
        return $this->cObjGetSingle($conf['cObject'] ?? '', $conf['cObject.'] ?? [], '/stdWrap/.cObject');
1840
    }
1841
1842
    /**
1843
     * numRows
1844
     * Counts the number of returned records of a DB operation
1845
     * makes use of select internally
1846
     *
1847
     * @param string $content Input value undergoing processing in this function.
1848
     * @param array $conf stdWrap properties for numRows.
1849
     * @return string The processed input value
1850
     */
1851
    public function stdWrap_numRows($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_numRows" is not in camel caps format
Loading history...
1852
    {
1853
        return $this->numRows($conf['numRows.']);
1854
    }
1855
1856
    /**
1857
     * preUserFunc
1858
     * Will execute a user public function before the content will be modified by any other stdWrap function
1859
     *
1860
     * @param string $content Input value undergoing processing in this function.
1861
     * @param array $conf stdWrap properties for preUserFunc.
1862
     * @return string The processed input value
1863
     */
1864
    public function stdWrap_preUserFunc($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_preUserFunc" is not in camel caps format
Loading history...
1865
    {
1866
        return $this->callUserFunction($conf['preUserFunc'], $conf['preUserFunc.'], $content);
1867
    }
1868
1869
    /**
1870
     * stdWrap override hook
1871
     * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
1872
     * this hook will execute functions on existing content but still before the content gets modified or replaced
1873
     *
1874
     * @param string $content Input value undergoing processing in these functions.
1875
     * @param array $conf All stdWrap properties, not just the ones for a particular function.
1876
     * @return string The processed input value
1877
     */
1878
    public function stdWrap_stdWrapOverride($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_stdWrapOverride" is not in camel caps format
Loading history...
1879
    {
1880
        foreach ($this->stdWrapHookObjects as $hookObject) {
1881
            /** @var ContentObjectStdWrapHookInterface $hookObject */
1882
            $content = $hookObject->stdWrapOverride($content, $conf, $this);
1883
        }
1884
        return $content;
1885
    }
1886
1887
    /**
1888
     * override
1889
     * Will override the current value of content with its own value'
1890
     *
1891
     * @param string $content Input value undergoing processing in this function.
1892
     * @param array $conf stdWrap properties for override.
1893
     * @return string The processed input value
1894
     */
1895
    public function stdWrap_override($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_override" is not in camel caps format
Loading history...
1896
    {
1897
        if (trim($conf['override'] ?? false)) {
0 ignored issues
show
Bug introduced by
It seems like $conf['override'] ?? false can also be of type false; however, parameter $str of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1897
        if (trim(/** @scrutinizer ignore-type */ $conf['override'] ?? false)) {
Loading history...
1898
            $content = $conf['override'];
1899
        }
1900
        return $content;
1901
    }
1902
1903
    /**
1904
     * preIfEmptyListNum
1905
     * Gets a value off a CSV list before the following ifEmpty check
1906
     * 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
1907
     *
1908
     * @param string $content Input value undergoing processing in this function.
1909
     * @param array $conf stdWrap properties for preIfEmptyListNum.
1910
     * @return string The processed input value
1911
     */
1912
    public function stdWrap_preIfEmptyListNum($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_preIfEmptyListNum" is not in camel caps format
Loading history...
1913
    {
1914
        return $this->listNum($content, $conf['preIfEmptyListNum'] ?? null, $conf['preIfEmptyListNum.']['splitChar'] ?? null);
1915
    }
1916
1917
    /**
1918
     * ifNull
1919
     * Will set content to a replacement value in case the value of content is NULL
1920
     *
1921
     * @param string|null $content Input value undergoing processing in this function.
1922
     * @param array $conf stdWrap properties for ifNull.
1923
     * @return string The processed input value
1924
     */
1925
    public function stdWrap_ifNull($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_ifNull" is not in camel caps format
Loading history...
1926
    {
1927
        return $content ?? $conf['ifNull'];
1928
    }
1929
1930
    /**
1931
     * ifEmpty
1932
     * Will set content to a replacement value in case the trimmed value of content returns FALSE
1933
     * 0 (zero) will be replaced as well
1934
     *
1935
     * @param string $content Input value undergoing processing in this function.
1936
     * @param array $conf stdWrap properties for ifEmpty.
1937
     * @return string The processed input value
1938
     */
1939
    public function stdWrap_ifEmpty($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_ifEmpty" is not in camel caps format
Loading history...
1940
    {
1941
        if (!trim($content)) {
1942
            $content = $conf['ifEmpty'];
1943
        }
1944
        return $content;
1945
    }
1946
1947
    /**
1948
     * ifBlank
1949
     * Will set content to a replacement value in case the trimmed value of content has no length
1950
     * 0 (zero) will not be replaced
1951
     *
1952
     * @param string $content Input value undergoing processing in this function.
1953
     * @param array $conf stdWrap properties for ifBlank.
1954
     * @return string The processed input value
1955
     */
1956
    public function stdWrap_ifBlank($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_ifBlank" is not in camel caps format
Loading history...
1957
    {
1958
        if (trim($content) === '') {
1959
            $content = $conf['ifBlank'];
1960
        }
1961
        return $content;
1962
    }
1963
1964
    /**
1965
     * listNum
1966
     * Gets a value off a CSV list after ifEmpty check
1967
     * Might return an empty value in case the CSV does not contain a value at the position given by listNum
1968
     * Use preIfEmptyListNum to avoid that behaviour
1969
     *
1970
     * @param string $content Input value undergoing processing in this function.
1971
     * @param array $conf stdWrap properties for listNum.
1972
     * @return string The processed input value
1973
     */
1974
    public function stdWrap_listNum($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_listNum" is not in camel caps format
Loading history...
1975
    {
1976
        return $this->listNum($content, $conf['listNum'] ?? null, $conf['listNum.']['splitChar'] ?? null);
1977
    }
1978
1979
    /**
1980
     * trim
1981
     * Cuts off any whitespace at the beginning and the end of the content
1982
     *
1983
     * @param string $content Input value undergoing processing in this function.
1984
     * @return string The processed input value
1985
     */
1986
    public function stdWrap_trim($content = '')
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_trim" is not in camel caps format
Loading history...
1987
    {
1988
        return trim($content);
1989
    }
1990
1991
    /**
1992
     * strPad
1993
     * Will return a string padded left/right/on both sides, based on configuration given as stdWrap properties
1994
     *
1995
     * @param string $content Input value undergoing processing in this function.
1996
     * @param array $conf stdWrap properties for strPad.
1997
     * @return string The processed input value
1998
     */
1999
    public function stdWrap_strPad($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_strPad" is not in camel caps format
Loading history...
2000
    {
2001
        // Must specify a length in conf for this to make sense
2002
        $length = 0;
2003
        // Padding with space is PHP-default
2004
        $padWith = ' ';
2005
        // Padding on the right side is PHP-default
2006
        $padType = STR_PAD_RIGHT;
2007
        if (!empty($conf['strPad.']['length'])) {
2008
            $length = isset($conf['strPad.']['length.']) ? $this->stdWrap($conf['strPad.']['length'], $conf['strPad.']['length.']) : $conf['strPad.']['length'];
2009
            $length = (int)$length;
2010
        }
2011
        if (isset($conf['strPad.']['padWith']) && (string)$conf['strPad.']['padWith'] !== '') {
2012
            $padWith = isset($conf['strPad.']['padWith.']) ? $this->stdWrap($conf['strPad.']['padWith'], $conf['strPad.']['padWith.']) : $conf['strPad.']['padWith'];
2013
        }
2014
        if (!empty($conf['strPad.']['type'])) {
2015
            $type = isset($conf['strPad.']['type.']) ? $this->stdWrap($conf['strPad.']['type'], $conf['strPad.']['type.']) : $conf['strPad.']['type'];
2016
            if (strtolower($type) === 'left') {
2017
                $padType = STR_PAD_LEFT;
2018
            } elseif (strtolower($type) === 'both') {
2019
                $padType = STR_PAD_BOTH;
2020
            }
2021
        }
2022
        return str_pad($content, $length, $padWith, $padType);
2023
    }
2024
2025
    /**
2026
     * stdWrap
2027
     * A recursive call of the stdWrap function set
2028
     * This enables the user to execute stdWrap functions in another than the predefined order
2029
     * It modifies the content, not the property
2030
     * while the new feature of chained stdWrap functions modifies the property and not the content
2031
     *
2032
     * @param string $content Input value undergoing processing in this function.
2033
     * @param array $conf stdWrap properties for stdWrap.
2034
     * @return string The processed input value
2035
     */
2036
    public function stdWrap_stdWrap($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_stdWrap" is not in camel caps format
Loading history...
2037
    {
2038
        return $this->stdWrap($content, $conf['stdWrap.']);
2039
    }
2040
2041
    /**
2042
     * stdWrap process hook
2043
     * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
2044
     * this hook executes functions directly after the recursive stdWrap function call but still before the content gets modified
2045
     *
2046
     * @param string $content Input value undergoing processing in these functions.
2047
     * @param array $conf All stdWrap properties, not just the ones for a particular function.
2048
     * @return string The processed input value
2049
     */
2050
    public function stdWrap_stdWrapProcess($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_stdWrapProcess" is not in camel caps format
Loading history...
2051
    {
2052
        foreach ($this->stdWrapHookObjects as $hookObject) {
2053
            /** @var ContentObjectStdWrapHookInterface $hookObject */
2054
            $content = $hookObject->stdWrapProcess($content, $conf, $this);
2055
        }
2056
        return $content;
2057
    }
2058
2059
    /**
2060
     * required
2061
     * Will immediately stop rendering and return an empty value
2062
     * when there is no content at this point
2063
     *
2064
     * @param string $content Input value undergoing processing in this function.
2065
     * @return string The processed input value
2066
     */
2067
    public function stdWrap_required($content = '')
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_required" is not in camel caps format
Loading history...
2068
    {
2069
        if ((string)$content === '') {
2070
            $content = '';
2071
            $this->stopRendering[$this->stdWrapRecursionLevel] = true;
2072
        }
2073
        return $content;
2074
    }
2075
2076
    /**
2077
     * if
2078
     * Will immediately stop rendering and return an empty value
2079
     * when the result of the checks returns FALSE
2080
     *
2081
     * @param string $content Input value undergoing processing in this function.
2082
     * @param array $conf stdWrap properties for if.
2083
     * @return string The processed input value
2084
     */
2085
    public function stdWrap_if($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_if" is not in camel caps format
Loading history...
2086
    {
2087
        if (empty($conf['if.']) || $this->checkIf($conf['if.'])) {
2088
            return $content;
2089
        }
2090
        $this->stopRendering[$this->stdWrapRecursionLevel] = true;
2091
        return '';
2092
    }
2093
2094
    /**
2095
     * fieldRequired
2096
     * Will immediately stop rendering and return an empty value
2097
     * when there is no content in the field given by fieldRequired
2098
     *
2099
     * @param string $content Input value undergoing processing in this function.
2100
     * @param array $conf stdWrap properties for fieldRequired.
2101
     * @return string The processed input value
2102
     */
2103
    public function stdWrap_fieldRequired($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_fieldRequired" is not in camel caps format
Loading history...
2104
    {
2105
        if (!trim($this->data[$conf['fieldRequired'] ?? null] ?? '')) {
2106
            $content = '';
2107
            $this->stopRendering[$this->stdWrapRecursionLevel] = true;
2108
        }
2109
        return $content;
2110
    }
2111
2112
    /**
2113
     * stdWrap csConv: Converts the input to UTF-8
2114
     *
2115
     * The character set of the input must be specified. Returns the input if
2116
     * matters go wrong, for example if an invalid character set is given.
2117
     *
2118
     * @param string $content The string to convert.
2119
     * @param array $conf stdWrap properties for csConv.
2120
     * @return string The processed input.
2121
     */
2122
    public function stdWrap_csConv($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_csConv" is not in camel caps format
Loading history...
2123
    {
2124
        if (!empty($conf['csConv'])) {
2125
            $output = mb_convert_encoding($content, 'utf-8', trim(strtolower($conf['csConv'])));
2126
            return $output !== false && $output !== '' ? $output : $content;
2127
        }
2128
        return $content;
2129
    }
2130
2131
    /**
2132
     * parseFunc
2133
     * Will parse the content based on functions given as stdWrap properties
2134
     * Heavily used together with RTE based content
2135
     *
2136
     * @param string $content Input value undergoing processing in this function.
2137
     * @param array $conf stdWrap properties for parseFunc.
2138
     * @return string The processed input value
2139
     */
2140
    public function stdWrap_parseFunc($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_parseFunc" is not in camel caps format
Loading history...
2141
    {
2142
        return $this->parseFunc($content, $conf['parseFunc.'], $conf['parseFunc']);
2143
    }
2144
2145
    /**
2146
     * HTMLparser
2147
     * Will parse HTML content based on functions given as stdWrap properties
2148
     * Heavily used together with RTE based content
2149
     *
2150
     * @param string $content Input value undergoing processing in this function.
2151
     * @param array $conf stdWrap properties for HTMLparser.
2152
     * @return string The processed input value
2153
     */
2154
    public function stdWrap_HTMLparser($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_HTMLparser" is not in camel caps format
Loading history...
2155
    {
2156
        if (isset($conf['HTMLparser.']) && is_array($conf['HTMLparser.'])) {
2157
            $content = $this->HTMLparser_TSbridge($content, $conf['HTMLparser.']);
2158
        }
2159
        return $content;
2160
    }
2161
2162
    /**
2163
     * split
2164
     * Will split the content by a given token and treat the results separately
2165
     * Automatically fills 'current' with a single result
2166
     *
2167
     * @param string $content Input value undergoing processing in this function.
2168
     * @param array $conf stdWrap properties for split.
2169
     * @return string The processed input value
2170
     */
2171
    public function stdWrap_split($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_split" is not in camel caps format
Loading history...
2172
    {
2173
        return $this->splitObj($content, $conf['split.']);
2174
    }
2175
2176
    /**
2177
     * replacement
2178
     * Will execute replacements on the content (optionally with preg-regex)
2179
     *
2180
     * @param string $content Input value undergoing processing in this function.
2181
     * @param array $conf stdWrap properties for replacement.
2182
     * @return string The processed input value
2183
     */
2184
    public function stdWrap_replacement($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_replacement" is not in camel caps format
Loading history...
2185
    {
2186
        return $this->replacement($content, $conf['replacement.']);
2187
    }
2188
2189
    /**
2190
     * prioriCalc
2191
     * Will use the content as a mathematical term and calculate the result
2192
     * Can be set to 1 to just get a calculated value or 'intval' to get the integer of the result
2193
     *
2194
     * @param string $content Input value undergoing processing in this function.
2195
     * @param array $conf stdWrap properties for prioriCalc.
2196
     * @return string The processed input value
2197
     */
2198
    public function stdWrap_prioriCalc($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_prioriCalc" is not in camel caps format
Loading history...
2199
    {
2200
        $content = MathUtility::calculateWithParentheses($content);
2201
        if (!empty($conf['prioriCalc']) && $conf['prioriCalc'] === 'intval') {
2202
            $content = (int)$content;
2203
        }
2204
        return $content;
2205
    }
2206
2207
    /**
2208
     * char
2209
     * Returns a one-character string containing the character specified by ascii code.
2210
     *
2211
     * Reliable results only for character codes in the integer range 0 - 127.
2212
     *
2213
     * @see http://php.net/manual/en/function.chr.php
2214
     * @param string $content Input value undergoing processing in this function.
2215
     * @param array $conf stdWrap properties for char.
2216
     * @return string The processed input value
2217
     */
2218
    public function stdWrap_char($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_char" is not in camel caps format
Loading history...
2219
    {
2220
        return chr((int)$conf['char']);
2221
    }
2222
2223
    /**
2224
     * intval
2225
     * Will return an integer value of the current content
2226
     *
2227
     * @param string $content Input value undergoing processing in this function.
2228
     * @return string The processed input value
2229
     */
2230
    public function stdWrap_intval($content = '')
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_intval" is not in camel caps format
Loading history...
2231
    {
2232
        return (int)$content;
2233
    }
2234
2235
    /**
2236
     * Will return a hashed value of the current content
2237
     *
2238
     * @param string $content Input value undergoing processing in this function.
2239
     * @param array $conf stdWrap properties for hash.
2240
     * @return string The processed input value
2241
     * @link http://php.net/manual/de/function.hash-algos.php for a list of supported hash algorithms
2242
     */
2243
    public function stdWrap_hash($content = '', array $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_hash" is not in camel caps format
Loading history...
2244
    {
2245
        $algorithm = isset($conf['hash.']) ? $this->stdWrap($conf['hash'], $conf['hash.']) : $conf['hash'];
2246
        if (function_exists('hash') && in_array($algorithm, hash_algos())) {
2247
            return hash($algorithm, $content);
2248
        }
2249
        // Non-existing hashing algorithm
2250
        return '';
2251
    }
2252
2253
    /**
2254
     * stdWrap_round will return a rounded number with ceil(), floor() or round(), defaults to round()
2255
     * Only the english number format is supported . (dot) as decimal point
2256
     *
2257
     * @param string $content Input value undergoing processing in this function.
2258
     * @param array $conf stdWrap properties for round.
2259
     * @return string The processed input value
2260
     */
2261
    public function stdWrap_round($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_round" is not in camel caps format
Loading history...
2262
    {
2263
        return $this->round($content, $conf['round.']);
2264
    }
2265
2266
    /**
2267
     * numberFormat
2268
     * Will return a formatted number based on configuration given as stdWrap properties
2269
     *
2270
     * @param string $content Input value undergoing processing in this function.
2271
     * @param array $conf stdWrap properties for numberFormat.
2272
     * @return string The processed input value
2273
     */
2274
    public function stdWrap_numberFormat($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_numberFormat" is not in camel caps format
Loading history...
2275
    {
2276
        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

2276
        return $this->numberFormat(/** @scrutinizer ignore-type */ $content, $conf['numberFormat.'] ?? []);
Loading history...
2277
    }
2278
2279
    /**
2280
     * expandList
2281
     * Will return a formatted number based on configuration given as stdWrap properties
2282
     *
2283
     * @param string $content Input value undergoing processing in this function.
2284
     * @return string The processed input value
2285
     */
2286
    public function stdWrap_expandList($content = '')
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_expandList" is not in camel caps format
Loading history...
2287
    {
2288
        return GeneralUtility::expandList($content);
2289
    }
2290
2291
    /**
2292
     * date
2293
     * Will return a formatted date based on configuration given according to PHP date/gmdate properties
2294
     * Will return gmdate when the property GMT returns TRUE
2295
     *
2296
     * @param string $content Input value undergoing processing in this function.
2297
     * @param array $conf stdWrap properties for date.
2298
     * @return string The processed input value
2299
     */
2300
    public function stdWrap_date($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_date" is not in camel caps format
Loading history...
2301
    {
2302
        // Check for zero length string to mimic default case of date/gmdate.
2303
        $content = (string)$content === '' ? $GLOBALS['EXEC_TIME'] : (int)$content;
2304
        $content = !empty($conf['date.']['GMT']) ? gmdate($conf['date'] ?? null, $content) : date($conf['date'] ?? null, $content);
2305
        return $content;
2306
    }
2307
2308
    /**
2309
     * strftime
2310
     * Will return a formatted date based on configuration given according to PHP strftime/gmstrftime properties
2311
     * Will return gmstrftime when the property GMT returns TRUE
2312
     *
2313
     * @param string $content Input value undergoing processing in this function.
2314
     * @param array $conf stdWrap properties for strftime.
2315
     * @return string The processed input value
2316
     */
2317
    public function stdWrap_strftime($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_strftime" is not in camel caps format
Loading history...
2318
    {
2319
        // Check for zero length string to mimic default case of strtime/gmstrftime
2320
        $content = (string)$content === '' ? $GLOBALS['EXEC_TIME'] : (int)$content;
2321
        $content = (isset($conf['strftime.']['GMT']) && $conf['strftime.']['GMT'])
2322
            ? gmstrftime($conf['strftime'] ?? null, $content)
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
2323
            : strftime($conf['strftime'] ?? null, $content);
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
2324
        if (!empty($conf['strftime.']['charset'])) {
2325
            $output = mb_convert_encoding($content, 'utf-8', trim(strtolower($conf['strftime.']['charset'])));
2326
            return $output ?: $content;
2327
        }
2328
        return $content;
2329
    }
2330
2331
    /**
2332
     * strtotime
2333
     * Will return a timestamp based on configuration given according to PHP strtotime
2334
     *
2335
     * @param string $content Input value undergoing processing in this function.
2336
     * @param array $conf stdWrap properties for strtotime.
2337
     * @return string The processed input value
2338
     */
2339
    public function stdWrap_strtotime($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_strtotime" is not in camel caps format
Loading history...
2340
    {
2341
        if ($conf['strtotime'] !== '1') {
2342
            $content .= ' ' . $conf['strtotime'];
2343
        }
2344
        return strtotime($content, $GLOBALS['EXEC_TIME']);
2345
    }
2346
2347
    /**
2348
     * age
2349
     * Will return the age of a given timestamp based on configuration given by stdWrap properties
2350
     *
2351
     * @param string $content Input value undergoing processing in this function.
2352
     * @param array $conf stdWrap properties for age.
2353
     * @return string The processed input value
2354
     */
2355
    public function stdWrap_age($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_age" is not in camel caps format
Loading history...
2356
    {
2357
        return $this->calcAge((int)($GLOBALS['EXEC_TIME'] ?? 0) - (int)$content, $conf['age'] ?? null);
2358
    }
2359
2360
    /**
2361
     * case
2362
     * Will transform the content to be upper or lower case only
2363
     * Leaves HTML tags untouched
2364
     *
2365
     * @param string $content Input value undergoing processing in this function.
2366
     * @param array $conf stdWrap properties for case.
2367
     * @return string The processed input value
2368
     */
2369
    public function stdWrap_case($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_case" is not in camel caps format
Loading history...
2370
    {
2371
        return $this->HTMLcaseshift($content, $conf['case']);
2372
    }
2373
2374
    /**
2375
     * bytes
2376
     * Will return the size of a given number in Bytes	 *
2377
     *
2378
     * @param string $content Input value undergoing processing in this function.
2379
     * @param array $conf stdWrap properties for bytes.
2380
     * @return string The processed input value
2381
     */
2382
    public function stdWrap_bytes($content = '', $conf = [])
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_bytes" is not in camel caps format
Loading history...
2383
    {
2384
        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

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

3010
            $char = chr(/** @scrutinizer ignore-type */ $char);
Loading history...
3011
        }
3012
        $temp = explode($char, $content);
3013
        $last = '' . (count($temp) - 1);
3014
        // Take a random item if requested
3015
        if ($listNum === 'rand') {
3016
            $listNum = random_int(0, count($temp) - 1);
3017
        }
3018
        $index = $this->calc(str_ireplace('last', $last, $listNum));
3019
        return $temp[$index];
3020
    }
3021
3022
    /**
3023
     * Compares values together based on the settings in the input TypoScript array and returns the comparison result.
3024
     * Implements the "if" function in TYPO3 TypoScript
3025
     *
3026
     * @param array $conf TypoScript properties defining what to compare
3027
     * @return bool
3028
     * @see stdWrap()
3029
     * @see _parseFunc()
3030
     */
3031
    public function checkIf($conf)
3032
    {
3033
        if (!is_array($conf)) {
0 ignored issues
show
introduced by
The condition is_array($conf) is always true.
Loading history...
3034
            return true;
3035
        }
3036
        if (isset($conf['directReturn'])) {
3037
            return (bool)$conf['directReturn'];
3038
        }
3039
        $flag = true;
3040
        if (isset($conf['isNull.'])) {
3041
            $isNull = $this->stdWrap('', $conf['isNull.']);
3042
            if ($isNull !== null) {
0 ignored issues
show
introduced by
The condition $isNull !== null is always true.
Loading history...
3043
                $flag = false;
3044
            }
3045
        }
3046
        if (isset($conf['isTrue']) || isset($conf['isTrue.'])) {
3047
            $isTrue = isset($conf['isTrue.']) ? trim($this->stdWrap($conf['isTrue'], $conf['isTrue.'])) : trim($conf['isTrue']);
3048
            if (!$isTrue) {
3049
                $flag = false;
3050
            }
3051
        }
3052
        if (isset($conf['isFalse']) || isset($conf['isFalse.'])) {
3053
            $isFalse = isset($conf['isFalse.']) ? trim($this->stdWrap($conf['isFalse'], $conf['isFalse.'])) : trim($conf['isFalse']);
3054
            if ($isFalse) {
3055
                $flag = false;
3056
            }
3057
        }
3058
        if (isset($conf['isPositive']) || isset($conf['isPositive.'])) {
3059
            $number = isset($conf['isPositive.']) ? $this->calc($this->stdWrap($conf['isPositive'], $conf['isPositive.'])) : $this->calc($conf['isPositive']);
3060
            if ($number < 1) {
3061
                $flag = false;
3062
            }
3063
        }
3064
        if ($flag) {
3065
            $value = isset($conf['value.'])
3066
                ? trim($this->stdWrap($conf['value'] ?? '', $conf['value.']))
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
3067
                : trim($conf['value'] ?? '');
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
3068
            if (isset($conf['isGreaterThan']) || isset($conf['isGreaterThan.'])) {
3069
                $number = isset($conf['isGreaterThan.']) ? trim($this->stdWrap($conf['isGreaterThan'], $conf['isGreaterThan.'])) : trim($conf['isGreaterThan']);
3070
                if ($number <= $value) {
3071
                    $flag = false;
3072
                }
3073
            }
3074
            if (isset($conf['isLessThan']) || isset($conf['isLessThan.'])) {
3075
                $number = isset($conf['isLessThan.']) ? trim($this->stdWrap($conf['isLessThan'], $conf['isLessThan.'])) : trim($conf['isLessThan']);
3076
                if ($number >= $value) {
3077
                    $flag = false;
3078
                }
3079
            }
3080
            if (isset($conf['equals']) || isset($conf['equals.'])) {
3081
                $number = isset($conf['equals.']) ? trim($this->stdWrap($conf['equals'], $conf['equals.'])) : trim($conf['equals']);
3082
                if ($number != $value) {
3083
                    $flag = false;
3084
                }
3085
            }
3086
            if (isset($conf['isInList']) || isset($conf['isInList.'])) {
3087
                $number = isset($conf['isInList.']) ? trim($this->stdWrap($conf['isInList'], $conf['isInList.'])) : trim($conf['isInList']);
3088
                if (!GeneralUtility::inList($value, $number)) {
3089
                    $flag = false;
3090
                }
3091
            }
3092
            if (isset($conf['bitAnd']) || isset($conf['bitAnd.'])) {
3093
                $number = isset($conf['bitAnd.']) ? trim($this->stdWrap($conf['bitAnd'], $conf['bitAnd.'])) : trim($conf['bitAnd']);
3094
                if ((new BitSet($number))->get($value) === false) {
0 ignored issues
show
Bug introduced by
$value of type string is incompatible with the type integer expected by parameter $bitIndex of TYPO3\CMS\Core\Type\BitSet::get(). ( Ignorable by Annotation )

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

3094
                if ((new BitSet($number))->get(/** @scrutinizer ignore-type */ $value) === false) {
Loading history...
Bug introduced by
$number of type string is incompatible with the type integer expected by parameter $set of TYPO3\CMS\Core\Type\BitSet::__construct(). ( Ignorable by Annotation )

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

3094
                if ((new BitSet(/** @scrutinizer ignore-type */ $number))->get($value) === false) {
Loading history...
3095
                    $flag = false;
3096
                }
3097
            }
3098
        }
3099
        if ($conf['negate'] ?? false) {
3100
            $flag = !$flag;
3101
        }
3102
        return $flag;
3103
    }
3104
3105
    /**
3106
     * Passes the input value, $theValue, to an instance of "\TYPO3\CMS\Core\Html\HtmlParser"
3107
     * together with the TypoScript options which are first converted from a TS style array
3108
     * to a set of arrays with options for the \TYPO3\CMS\Core\Html\HtmlParser class.
3109
     *
3110
     * @param string $theValue The value to parse by the class \TYPO3\CMS\Core\Html\HtmlParser
3111
     * @param array $conf TypoScript properties for the parser. See link.
3112
     * @return string Return value.
3113
     * @see stdWrap()
3114
     * @see \TYPO3\CMS\Core\Html\HtmlParser::HTMLparserConfig()
3115
     * @see \TYPO3\CMS\Core\Html\HtmlParser::HTMLcleaner()
3116
     */
3117
    public function HTMLparser_TSbridge($theValue, $conf)
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::HTMLparser_TSbridge" is not in camel caps format
Loading history...
3118
    {
3119
        $htmlParser = GeneralUtility::makeInstance(HtmlParser::class);
3120
        $htmlParserCfg = $htmlParser->HTMLparserConfig($conf);
3121
        return $htmlParser->HTMLcleaner($theValue, $htmlParserCfg[0], $htmlParserCfg[1], $htmlParserCfg[2], $htmlParserCfg[3]);
3122
    }
3123
3124
    /**
3125
     * Wrapping input value in a regular "wrap" but parses the wrapping value first for "insertData" codes.
3126
     *
3127
     * @param string $content Input string being wrapped
3128
     * @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.
3129
     * @return string Output string wrapped in the wrapping value.
3130
     * @see insertData()
3131
     * @see stdWrap()
3132
     */
3133
    public function dataWrap($content, $wrap)
3134
    {
3135
        return $this->wrap($content, $this->insertData($wrap));
3136
    }
3137
3138
    /**
3139
     * Implements the "insertData" property of stdWrap meaning that if strings matching {...} is found in the input string they
3140
     * will be substituted with the return value from getData (datatype) which is passed the content of the curly braces.
3141
     * If the content inside the curly braces starts with a hash sign {#...} it is a field name that must be quoted by Doctrine
3142
     * DBAL and is skipped here for later processing.
3143
     *
3144
     * Example: If input string is "This is the page title: {page:title}" then the part, '{page:title}', will be substituted with
3145
     * the current pages title field value.
3146
     *
3147
     * @param string $str Input value
3148
     * @return string Processed input value
3149
     * @see getData()
3150
     * @see stdWrap()
3151
     * @see dataWrap()
3152
     */
3153
    public function insertData($str)
3154
    {
3155
        $inside = 0;
3156
        $newVal = '';
3157
        $pointer = 0;
3158
        $totalLen = strlen($str);
3159
        do {
3160
            if (!$inside) {
3161
                $len = strcspn(substr($str, $pointer), '{');
3162
                $newVal .= substr($str, $pointer, $len);
3163
                $inside = true;
3164
                if (substr($str, $pointer + $len + 1, 1) === '#') {
3165
                    $len2 = strcspn(substr($str, $pointer + $len), '}');
3166
                    $newVal .= substr($str, $pointer + $len, $len2);
3167
                    $len += $len2;
3168
                    $inside = false;
3169
                }
3170
            } else {
3171
                $len = strcspn(substr($str, $pointer), '}') + 1;
3172
                $newVal .= $this->getData(substr($str, $pointer + 1, $len - 2), $this->data);
3173
                $inside = false;
3174
            }
3175
            $pointer += $len;
3176
        } while ($pointer < $totalLen);
3177
        return $newVal;
3178
    }
3179
3180
    /**
3181
     * 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.
3182
     * Notice; this function (used by stdWrap) can be disabled by a "config.disablePrefixComment" setting in TypoScript.
3183
     *
3184
     * @param string $str Input value
3185
     * @param array $conf TypoScript Configuration (not used at this point.)
3186
     * @param string $content The content to wrap the comment around.
3187
     * @return string Processed input value
3188
     * @see stdWrap()
3189
     */
3190
    public function prefixComment($str, $conf, $content)
3191
    {
3192
        if (empty($str)) {
3193
            return $content;
3194
        }
3195
        $parts = explode('|', $str);
3196
        $indent = (int)$parts[0];
3197
        $comment = htmlspecialchars($this->insertData($parts[1]));
3198
        $output = LF
3199
            . str_pad('', $indent, "\t") . '<!-- ' . $comment . ' [begin] -->' . LF
3200
            . str_pad('', $indent + 1, "\t") . $content . LF
3201
            . str_pad('', $indent, "\t") . '<!-- ' . $comment . ' [end] -->' . LF
3202
            . str_pad('', $indent + 1, "\t");
3203
        return $output;
3204
    }
3205
3206
    /**
3207
     * Implements the stdWrap property "substring" which is basically a TypoScript implementation of the PHP function, substr()
3208
     *
3209
     * @param string $content The string to perform the operation on
3210
     * @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().
3211
     * @return string The processed input value.
3212
     * @internal
3213
     * @see stdWrap()
3214
     */
3215
    public function substring($content, $options)
3216
    {
3217
        $options = GeneralUtility::intExplode(',', $options . ',');
3218
        if ($options[1]) {
3219
            return mb_substr($content, $options[0], $options[1], 'utf-8');
0 ignored issues
show
Bug introduced by
$options[0] of type string is incompatible with the type integer expected by parameter $start of mb_substr(). ( Ignorable by Annotation )

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

3219
            return mb_substr($content, /** @scrutinizer ignore-type */ $options[0], $options[1], 'utf-8');
Loading history...
Bug introduced by
$options[1] of type string is incompatible with the type integer expected by parameter $length of mb_substr(). ( Ignorable by Annotation )

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

3219
            return mb_substr($content, $options[0], /** @scrutinizer ignore-type */ $options[1], 'utf-8');
Loading history...
3220
        }
3221
        return mb_substr($content, $options[0], null, 'utf-8');
3222
    }
3223
3224
    /**
3225
     * 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.
3226
     *
3227
     * @param string $content The string to perform the operation on
3228
     * @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.
3229
     * @return string The processed input value.
3230
     * @internal
3231
     * @see stdWrap()
3232
     */
3233
    public function crop($content, $options)
3234
    {
3235
        $options = explode('|', $options);
3236
        $chars = (int)$options[0];
3237
        $afterstring = trim($options[1] ?? '');
3238
        $crop2space = trim($options[2] ?? '');
3239
        if ($chars) {
3240
            if (mb_strlen($content, 'utf-8') > abs($chars)) {
3241
                $truncatePosition = false;
3242
                if ($chars < 0) {
3243
                    $content = mb_substr($content, $chars, null, 'utf-8');
3244
                    if ($crop2space) {
3245
                        $truncatePosition = strpos($content, ' ');
3246
                    }
3247
                    $content = $truncatePosition ? $afterstring . substr($content, $truncatePosition) : $afterstring . $content;
3248
                } else {
3249
                    $content = mb_substr($content, 0, $chars, 'utf-8');
3250
                    if ($crop2space) {
3251
                        $truncatePosition = strrpos($content, ' ');
3252
                    }
3253
                    $content = $truncatePosition ? substr($content, 0, $truncatePosition) . $afterstring : $content . $afterstring;
3254
                }
3255
            }
3256
        }
3257
        return $content;
3258
    }
3259
3260
    /**
3261
     * Implements the stdWrap property "cropHTML" which is a modified "substr" function allowing to limit a string length
3262
     * to a certain number of chars (from either start or end of string) and having a pre/postfix applied if the string
3263
     * really was cropped.
3264
     *
3265
     * Compared to stdWrap.crop it respects HTML tags and entities.
3266
     *
3267
     * @param string $content The string to perform the operation on
3268
     * @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.
3269
     * @return string The processed input value.
3270
     * @internal
3271
     * @see stdWrap()
3272
     */
3273
    public function cropHTML($content, $options)
3274
    {
3275
        $options = explode('|', $options);
3276
        $chars = (int)$options[0];
3277
        $absChars = abs($chars);
3278
        $replacementForEllipsis = trim($options[1] ?? '');
3279
        $crop2space = trim($options[2] ?? '') === '1';
3280
        // Split $content into an array(even items in the array are outside the tags, odd numbers are tag-blocks).
3281
        $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';
3282
        $tagsRegEx = '
3283
			(
3284
				(?:
3285
					<!--.*?-->					# a comment
3286
					|
3287
					<canvas[^>]*>.*?</canvas>   # a canvas tag
3288
					|
3289
					<script[^>]*>.*?</script>   # a script tag
3290
					|
3291
					<noscript[^>]*>.*?</noscript> # a noscript tag
3292
					|
3293
					<template[^>]*>.*?</template> # a template tag
3294
				)
3295
				|
3296
				</?(?:' . $tags . ')+			# opening tag (\'<tag\') or closing tag (\'</tag\')
3297
				(?:
3298
					(?:
3299
						(?:
3300
							\\s+\\w[\\w-]*		# EITHER spaces, followed by attribute names
3301
							(?:
3302
								\\s*=?\\s*		# equals
3303
								(?>
3304
									".*?"		# attribute values in double-quotes
3305
									|
3306
									\'.*?\'		# attribute values in single-quotes
3307
									|
3308
									[^\'">\\s]+	# plain attribute values
3309
								)
3310
							)?
3311
						)
3312
						|						# OR a single dash (for TYPO3 link tag)
3313
						(?:
3314
							\\s+-
3315
						)
3316
					)+\\s*
3317
					|							# OR only spaces
3318
					\\s*
3319
				)
3320
				/?>								# closing the tag with \'>\' or \'/>\'
3321
			)';
3322
        $splittedContent = preg_split('%' . $tagsRegEx . '%xs', $content, -1, PREG_SPLIT_DELIM_CAPTURE);
3323
        // Reverse array if we are cropping from right.
3324
        if ($chars < 0) {
3325
            $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

3325
            $splittedContent = array_reverse(/** @scrutinizer ignore-type */ $splittedContent);
Loading history...
3326
        }
3327
        // Crop the text (chars of tag-blocks are not counted).
3328
        $strLen = 0;
3329
        // This is the offset of the content item which was cropped.
3330
        $croppedOffset = null;
3331
        $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

3331
        $countSplittedContent = count(/** @scrutinizer ignore-type */ $splittedContent);
Loading history...
3332
        for ($offset = 0; $offset < $countSplittedContent; $offset++) {
3333
            if ($offset % 2 === 0) {
3334
                $tempContent = $splittedContent[$offset];
3335
                $thisStrLen = mb_strlen(html_entity_decode($tempContent, ENT_COMPAT, 'UTF-8'), 'utf-8');
3336
                if ($strLen + $thisStrLen > $absChars) {
3337
                    $croppedOffset = $offset;
3338
                    $cropPosition = $absChars - $strLen;
3339
                    // The snippet "&[^&\s;]{2,8};" in the RegEx below represents entities.
3340
                    $patternMatchEntityAsSingleChar = '(&[^&\\s;]{2,8};|.)';
3341
                    $cropRegEx = $chars < 0 ? '#' . $patternMatchEntityAsSingleChar . '{0,' . ($cropPosition + 1) . '}$#uis' : '#^' . $patternMatchEntityAsSingleChar . '{0,' . ($cropPosition + 1) . '}#uis';
3342
                    if (preg_match($cropRegEx, $tempContent, $croppedMatch)) {
3343
                        $tempContentPlusOneCharacter = $croppedMatch[0];
3344
                    } else {
3345
                        $tempContentPlusOneCharacter = false;
3346
                    }
3347
                    $cropRegEx = $chars < 0 ? '#' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}$#uis' : '#^' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}#uis';
3348
                    if (preg_match($cropRegEx, $tempContent, $croppedMatch)) {
3349
                        $tempContent = $croppedMatch[0];
3350
                        if ($crop2space && $tempContentPlusOneCharacter !== false) {
3351
                            $cropRegEx = $chars < 0 ? '#(?<=\\s)' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}$#uis' : '#^' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}(?=\\s)#uis';
3352
                            if (preg_match($cropRegEx, $tempContentPlusOneCharacter, $croppedMatch)) {
3353
                                $tempContent = $croppedMatch[0];
3354
                            }
3355
                        }
3356
                    }
3357
                    $splittedContent[$offset] = $tempContent;
3358
                    break;
3359
                }
3360
                $strLen += $thisStrLen;
3361
            }
3362
        }
3363
        // Close cropped tags.
3364
        $closingTags = [];
3365
        if ($croppedOffset !== null) {
3366
            $openingTagRegEx = '#^<(\\w+)(?:\\s|>)#';
3367
            $closingTagRegEx = '#^</(\\w+)(?:\\s|>)#';
3368
            for ($offset = $croppedOffset - 1; $offset >= 0; $offset = $offset - 2) {
3369
                if (substr($splittedContent[$offset], -2) === '/>') {
3370
                    // Ignore empty element tags (e.g. <br />).
3371
                    continue;
3372
                }
3373
                preg_match($chars < 0 ? $closingTagRegEx : $openingTagRegEx, $splittedContent[$offset], $matches);
3374
                $tagName = $matches[1] ?? null;
3375
                if ($tagName !== null) {
3376
                    // Seek for the closing (or opening) tag.
3377
                    $countSplittedContent = count($splittedContent);
3378
                    for ($seekingOffset = $offset + 2; $seekingOffset < $countSplittedContent; $seekingOffset = $seekingOffset + 2) {
3379
                        preg_match($chars < 0 ? $openingTagRegEx : $closingTagRegEx, $splittedContent[$seekingOffset], $matches);
3380
                        $seekingTagName = $matches[1] ?? null;
3381
                        if ($tagName === $seekingTagName) {
3382
                            // We found a matching tag.
3383
                            // Add closing tag only if it occurs after the cropped content item.
3384
                            if ($seekingOffset > $croppedOffset) {
3385
                                $closingTags[] = $splittedContent[$seekingOffset];
3386
                            }
3387
                            break;
3388
                        }
3389
                    }
3390
                }
3391
            }
3392
            // Drop the cropped items of the content array. The $closingTags will be added later on again.
3393
            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

3393
            array_splice(/** @scrutinizer ignore-type */ $splittedContent, $croppedOffset + 1);
Loading history...
3394
        }
3395
        $splittedContent = array_merge($splittedContent, [
3396
            $croppedOffset !== null ? $replacementForEllipsis : ''
3397
        ], $closingTags);
3398
        // Reverse array once again if we are cropping from the end.
3399
        if ($chars < 0) {
3400
            $splittedContent = array_reverse($splittedContent);
3401
        }
3402
        return implode('', $splittedContent);
3403
    }
3404
3405
    /**
3406
     * Performs basic mathematical evaluation of the input string. Does NOT take parenthesis and operator precedence into account! (for that, see \TYPO3\CMS\Core\Utility\MathUtility::calculateWithPriorityToAdditionAndSubtraction())
3407
     *
3408
     * @param string $val The string to evaluate. Example: "3+4*10/5" will generate "35". Only integer numbers can be used.
3409
     * @return int The result (might be a float if you did a division of the numbers).
3410
     * @see \TYPO3\CMS\Core\Utility\MathUtility::calculateWithPriorityToAdditionAndSubtraction()
3411
     */
3412
    public function calc($val)
3413
    {
3414
        $parts = GeneralUtility::splitCalc($val, '+-*/');
3415
        $value = 0;
3416
        foreach ($parts as $part) {
3417
            $theVal = $part[1];
3418
            $sign = $part[0];
3419
            if ((string)(int)$theVal === (string)$theVal) {
3420
                $theVal = (int)$theVal;
3421
            } else {
3422
                $theVal = 0;
3423
            }
3424
            if ($sign === '-') {
3425
                $value -= $theVal;
3426
            }
3427
            if ($sign === '+') {
3428
                $value += $theVal;
3429
            }
3430
            if ($sign === '/') {
3431
                if ((int)$theVal) {
3432
                    $value /= (int)$theVal;
3433
                }
3434
            }
3435
            if ($sign === '*') {
3436
                $value *= $theVal;
3437
            }
3438
        }
3439
        return $value;
3440
    }
3441
3442
    /**
3443
     * 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.
3444
     * 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.
3445
     * Implements the "optionSplit" processing of the TypoScript options for each splitted value to parse.
3446
     *
3447
     * @param string $value The string value to explode by $conf[token] and process each part
3448
     * @param array $conf TypoScript properties for "split
3449
     * @return string Compiled result
3450
     * @internal
3451
     * @see stdWrap()
3452
     * @see \TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject::processItemStates()
3453
     */
3454
    public function splitObj($value, $conf)
3455
    {
3456
        $conf['token'] = isset($conf['token.']) ? $this->stdWrap($conf['token'], $conf['token.']) : $conf['token'];
3457
        if ($conf['token'] === '') {
3458
            return $value;
3459
        }
3460
        $valArr = explode($conf['token'], $value);
3461
3462
        // return value directly by returnKey. No further processing
3463
        if (!empty($valArr) && (MathUtility::canBeInterpretedAsInteger($conf['returnKey'] ?? null) || ($conf['returnKey.'] ?? false))
3464
        ) {
3465
            $key = isset($conf['returnKey.']) ? (int)$this->stdWrap($conf['returnKey'], $conf['returnKey.']) : (int)$conf['returnKey'];
3466
            return $valArr[$key] ?? '';
3467
        }
3468
3469
        // return the amount of elements. No further processing
3470
        if (!empty($valArr) && ($conf['returnCount'] || $conf['returnCount.'])) {
3471
            $returnCount = isset($conf['returnCount.']) ? (bool)$this->stdWrap($conf['returnCount'], $conf['returnCount.']) : (bool)$conf['returnCount'];
3472
            return $returnCount ? count($valArr) : 0;
3473
        }
3474
3475
        // calculate splitCount
3476
        $splitCount = count($valArr);
3477
        $max = isset($conf['max.']) ? (int)$this->stdWrap($conf['max'], $conf['max.']) : (int)$conf['max'];
3478
        if ($max && $splitCount > $max) {
3479
            $splitCount = $max;
3480
        }
3481
        $min = isset($conf['min.']) ? (int)$this->stdWrap($conf['min'], $conf['min.']) : (int)$conf['min'];
3482
        if ($min && $splitCount < $min) {
3483
            $splitCount = $min;
3484
        }
3485
        $wrap = isset($conf['wrap.']) ? (string)$this->stdWrap($conf['wrap'], $conf['wrap.']) : (string)$conf['wrap'];
3486
        $cObjNumSplitConf = isset($conf['cObjNum.']) ? (string)$this->stdWrap($conf['cObjNum'], $conf['cObjNum.']) : (string)$conf['cObjNum'];
3487
        $splitArr = [];
3488
        if ($wrap !== '' || $cObjNumSplitConf !== '') {
3489
            $splitArr['wrap'] = $wrap;
3490
            $splitArr['cObjNum'] = $cObjNumSplitConf;
3491
            $splitArr = GeneralUtility::makeInstance(TypoScriptService::class)
3492
                ->explodeConfigurationForOptionSplit($splitArr, $splitCount);
3493
        }
3494
        $content = '';
3495
        for ($a = 0; $a < $splitCount; $a++) {
3496
            $this->getTypoScriptFrontendController()->register['SPLIT_COUNT'] = $a;
3497
            $value = '' . $valArr[$a];
3498
            $this->data[$this->currentValKey] = $value;
3499
            if ($splitArr[$a]['cObjNum']) {
3500
                $objName = (int)$splitArr[$a]['cObjNum'];
3501
                $value = isset($conf[$objName . '.'])
3502
                    ? $this->stdWrap($this->cObjGet($conf[$objName . '.'], $objName . '.'), $conf[$objName . '.'])
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
3503
                    : $this->cObjGet($conf[$objName . '.'], $objName . '.');
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
3504
            }
3505
            $wrap = isset($splitArr[$a]['wrap.']) ? $this->stdWrap($splitArr[$a]['wrap'], $splitArr[$a]['wrap.']) : $splitArr[$a]['wrap'];
3506
            if ($wrap) {
3507
                $value = $this->wrap($value, $wrap);
3508
            }
3509
            $content .= $value;
3510
        }
3511
        return $content;
3512
    }
3513
3514
    /**
3515
     * Processes ordered replacements on content data.
3516
     *
3517
     * @param string $content The content to be processed
3518
     * @param array $configuration The TypoScript configuration for stdWrap.replacement
3519
     * @return string The processed content data
3520
     */
3521
    protected function replacement($content, array $configuration)
3522
    {
3523
        // Sorts actions in configuration by numeric index
3524
        ksort($configuration, SORT_NUMERIC);
3525
        foreach ($configuration as $index => $action) {
3526
            // Checks whether we have a valid action and a numeric key ending with a dot ("10.")
3527
            if (is_array($action) && substr($index, -1) === '.' && MathUtility::canBeInterpretedAsInteger(substr($index, 0, -1))) {
3528
                $content = $this->replacementSingle($content, $action);
3529
            }
3530
        }
3531
        return $content;
3532
    }
3533
3534
    /**
3535
     * Processes a single search/replace on content data.
3536
     *
3537
     * @param string $content The content to be processed
3538
     * @param array $configuration The TypoScript of the search/replace action to be processed
3539
     * @return string The processed content data
3540
     */
3541
    protected function replacementSingle($content, array $configuration)
3542
    {
3543
        if ((isset($configuration['search']) || isset($configuration['search.'])) && (isset($configuration['replace']) || isset($configuration['replace.']))) {
3544
            // Gets the strings
3545
            $search = isset($configuration['search.']) ? $this->stdWrap($configuration['search'], $configuration['search.']) : $configuration['search'];
3546
            $replace = isset($configuration['replace.'])
3547
                ? $this->stdWrap($configuration['replace'] ?? null, $configuration['replace.'])
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
3548
                : $configuration['replace'] ?? null;
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
3549
            $useRegularExpression = false;
3550
            // Determines whether regular expression shall be used
3551
            if (isset($configuration['useRegExp'])
3552
                || (isset($configuration['useRegExp.']) && $configuration['useRegExp.'])
3553
            ) {
3554
                $useRegularExpression = isset($configuration['useRegExp.']) ? (bool)$this->stdWrap($configuration['useRegExp'], $configuration['useRegExp.']) : (bool)$configuration['useRegExp'];
3555
            }
3556
            $useOptionSplitReplace = false;
3557
            // Determines whether replace-pattern uses option-split
3558
            if (isset($configuration['useOptionSplitReplace']) || isset($configuration['useOptionSplitReplace.'])) {
3559
                $useOptionSplitReplace = isset($configuration['useOptionSplitReplace.']) ? (bool)$this->stdWrap($configuration['useOptionSplitReplace'], $configuration['useOptionSplitReplace.']) : (bool)$configuration['useOptionSplitReplace'];
3560
            }
3561
3562
            // Performs a replacement by preg_replace()
3563
            if ($useRegularExpression) {
3564
                // Get separator-character which precedes the string and separates search-string from the modifiers
3565
                $separator = $search[0];
3566
                $startModifiers = strrpos($search, $separator);
3567
                if ($separator !== false && $startModifiers > 0) {
3568
                    $modifiers = substr($search, $startModifiers + 1);
3569
                    // remove "e" (eval-modifier), which would otherwise allow to run arbitrary PHP-code
3570
                    $modifiers = str_replace('e', '', $modifiers);
3571
                    $search = substr($search, 0, $startModifiers + 1) . $modifiers;
3572
                }
3573
                if ($useOptionSplitReplace) {
3574
                    // init for replacement
3575
                    $splitCount = preg_match_all($search, $content, $matches);
3576
                    $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
3577
                    $replaceArray = $typoScriptService->explodeConfigurationForOptionSplit([$replace], $splitCount);
3578
                    $replaceCount = 0;
3579
3580
                    $replaceCallback = function ($match) use ($replaceArray, $search, &$replaceCount) {
3581
                        $replaceCount++;
3582
                        return preg_replace($search, $replaceArray[$replaceCount - 1][0], $match[0]);
3583
                    };
3584
                    $content = preg_replace_callback($search, $replaceCallback, $content);
3585
                } else {
3586
                    $content = preg_replace($search, $replace, $content);
3587
                }
3588
            } elseif ($useOptionSplitReplace) {
3589
                // turn search-string into a preg-pattern
3590
                $searchPreg = '#' . preg_quote($search, '#') . '#';
3591
3592
                // init for replacement
3593
                $splitCount = preg_match_all($searchPreg, $content, $matches);
3594
                $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
3595
                $replaceArray = $typoScriptService->explodeConfigurationForOptionSplit([$replace], $splitCount);
3596
                $replaceCount = 0;
3597
3598
                $replaceCallback = function () use ($replaceArray, &$replaceCount) {
3599
                    $replaceCount++;
3600
                    return $replaceArray[$replaceCount - 1][0];
3601
                };
3602
                $content = preg_replace_callback($searchPreg, $replaceCallback, $content);
3603
            } else {
3604
                $content = str_replace($search, $replace, $content);
3605
            }
3606
        }
3607
        return $content;
3608
    }
3609
3610
    /**
3611
     * Implements the "round" property of stdWrap
3612
     * This is a Wrapper function for PHP's rounding functions (round,ceil,floor), defaults to round()
3613
     *
3614
     * @param string $content Value to process
3615
     * @param array $conf TypoScript configuration for round
3616
     * @return string The formatted number
3617
     */
3618
    protected function round($content, array $conf = [])
3619
    {
3620
        $decimals = isset($conf['decimals.'])
3621
            ? $this->stdWrap($conf['decimals'] ?? '', $conf['decimals.'])
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
3622
            : ($conf['decimals'] ?? null);
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
3623
        $type = isset($conf['roundType.'])
3624
            ? $this->stdWrap($conf['roundType'] ?? '', $conf['roundType.'])
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
3625
            : ($conf['roundType'] ?? null);
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
3626
        $floatVal = (float)$content;
3627
        switch ($type) {
3628
            case 'ceil':
3629
                $content = ceil($floatVal);
3630
                break;
3631
            case 'floor':
3632
                $content = floor($floatVal);
3633
                break;
3634
            case 'round':
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
3635
3636
            default:
3637
                $content = round($floatVal, (int)$decimals);
3638
        }
3639
        return $content;
3640
    }
3641
3642
    /**
3643
     * Implements the stdWrap property "numberFormat"
3644
     * This is a Wrapper function for php's number_format()
3645
     *
3646
     * @param float $content Value to process
3647
     * @param array $conf TypoScript Configuration for numberFormat
3648
     * @return string The formatted number
3649
     */
3650
    public function numberFormat($content, $conf)
3651
    {
3652
        $decimals = isset($conf['decimals.'])
3653
            ? (int)$this->stdWrap($conf['decimals'] ?? '', $conf['decimals.'])
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
3654
            : (int)($conf['decimals'] ?? 0);
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
3655
        $dec_point = isset($conf['dec_point.'])
3656
            ? $this->stdWrap($conf['dec_point'] ?? '', $conf['dec_point.'])
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
3657
            : ($conf['dec_point'] ?? null);
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
3658
        $thousands_sep = isset($conf['thousands_sep.'])
3659
            ? $this->stdWrap($conf['thousands_sep'] ?? '', $conf['thousands_sep.'])
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
3660
            : ($conf['thousands_sep'] ?? null);
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
3661
        return number_format((float)$content, $decimals, $dec_point, $thousands_sep);
3662
    }
3663
3664
    /**
3665
     * Implements the stdWrap property, "parseFunc".
3666
     * This is a function with a lot of interesting uses. In classic TypoScript this is used to process text
3667
     * from the bodytext field; This included highlighting of search words, changing http:// and mailto: prefixed strings into etc.
3668
     * It is still a very important function for processing of bodytext which is normally stored in the database
3669
     * in a format which is not fully ready to be outputted.
3670
     * This situation has not become better by having a RTE around...
3671
     *
3672
     * This function is actually just splitting the input content according to the configuration of "external blocks".
3673
     * This means that before the input string is actually "parsed" it will be splitted into the parts configured to BE parsed
3674
     * (while other parts/blocks should NOT be parsed).
3675
     * Therefore the actual processing of the parseFunc properties goes on in ->_parseFunc()
3676
     *
3677
     * @param string $theValue The value to process.
3678
     * @param array $conf TypoScript configuration for parseFunc
3679
     * @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!
3680
     * @return string The processed value
3681
     * @see _parseFunc()
3682
     */
3683
    public function parseFunc($theValue, $conf, $ref = '')
3684
    {
3685
        // Fetch / merge reference, if any
3686
        if ($ref) {
3687
            $temp_conf = [
3688
                'parseFunc' => $ref,
3689
                'parseFunc.' => $conf
3690
            ];
3691
            $temp_conf = $this->mergeTSRef($temp_conf, 'parseFunc');
3692
            $conf = $temp_conf['parseFunc.'];
3693
        }
3694
        // Process:
3695
        if ((string)($conf['externalBlocks'] ?? '') === '') {
3696
            return $this->_parseFunc($theValue, $conf);
3697
        }
3698
        $tags = strtolower(implode(',', GeneralUtility::trimExplode(',', $conf['externalBlocks'])));
3699
        $htmlParser = GeneralUtility::makeInstance(HtmlParser::class);
3700
        $parts = $htmlParser->splitIntoBlock($tags, $theValue);
3701
        foreach ($parts as $k => $v) {
3702
            if ($k % 2) {
3703
                // font:
3704
                $tagName = strtolower($htmlParser->getFirstTagName($v));
3705
                $cfg = $conf['externalBlocks.'][$tagName . '.'];
3706
                if ($cfg['stripNLprev'] || $cfg['stripNL']) {
3707
                    $parts[$k - 1] = preg_replace('/' . CR . '?' . LF . '[ ]*$/', '', $parts[$k - 1]);
3708
                }
3709
                if ($cfg['stripNLnext'] || $cfg['stripNL']) {
3710
                    $parts[$k + 1] = preg_replace('/^[ ]*' . CR . '?' . LF . '/', '', $parts[$k + 1]);
3711
                }
3712
            }
3713
        }
3714
        foreach ($parts as $k => $v) {
3715
            if ($k % 2) {
3716
                $tag = $htmlParser->getFirstTag($v);
3717
                $tagName = strtolower($htmlParser->getFirstTagName($v));
3718
                $cfg = $conf['externalBlocks.'][$tagName . '.'];
3719
                if ($cfg['callRecursive']) {
3720
                    $parts[$k] = $this->parseFunc($htmlParser->removeFirstAndLastTag($v), $conf);
3721
                    if (!$cfg['callRecursive.']['dontWrapSelf']) {
3722
                        if ($cfg['callRecursive.']['alternativeWrap']) {
3723
                            $parts[$k] = $this->wrap($parts[$k], $cfg['callRecursive.']['alternativeWrap']);
3724
                        } else {
3725
                            if (is_array($cfg['callRecursive.']['tagStdWrap.'])) {
3726
                                $tag = $this->stdWrap($tag, $cfg['callRecursive.']['tagStdWrap.']);
3727
                            }
3728
                            $parts[$k] = $tag . $parts[$k] . '</' . $tagName . '>';
3729
                        }
3730
                    }
3731
                } elseif ($cfg['HTMLtableCells']) {
3732
                    $rowParts = $htmlParser->splitIntoBlock('tr', $parts[$k]);
3733
                    foreach ($rowParts as $kk => $vv) {
3734
                        if ($kk % 2) {
3735
                            $colParts = $htmlParser->splitIntoBlock('td,th', $vv);
3736
                            $cc = 0;
3737
                            foreach ($colParts as $kkk => $vvv) {
3738
                                if ($kkk % 2) {
3739
                                    $cc++;
3740
                                    $tag = $htmlParser->getFirstTag($vvv);
3741
                                    $tagName = strtolower($htmlParser->getFirstTagName($vvv));
3742
                                    $colParts[$kkk] = $htmlParser->removeFirstAndLastTag($vvv);
3743
                                    if ($cfg['HTMLtableCells.'][$cc . '.']['callRecursive'] || !isset($cfg['HTMLtableCells.'][$cc . '.']['callRecursive']) && $cfg['HTMLtableCells.']['default.']['callRecursive']) {
3744
                                        if ($cfg['HTMLtableCells.']['addChr10BetweenParagraphs']) {
3745
                                            $colParts[$kkk] = str_replace('</p><p>', '</p>' . LF . '<p>', $colParts[$kkk]);
3746
                                        }
3747
                                        $colParts[$kkk] = $this->parseFunc($colParts[$kkk], $conf);
3748
                                    }
3749
                                    $tagStdWrap = is_array($cfg['HTMLtableCells.'][$cc . '.']['tagStdWrap.'])
3750
                                        ? $cfg['HTMLtableCells.'][$cc . '.']['tagStdWrap.']
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
3751
                                        : $cfg['HTMLtableCells.']['default.']['tagStdWrap.'];
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
3752
                                    if (is_array($tagStdWrap)) {
3753
                                        $tag = $this->stdWrap($tag, $tagStdWrap);
3754
                                    }
3755
                                    $stdWrap = is_array($cfg['HTMLtableCells.'][$cc . '.']['stdWrap.'])
3756
                                        ? $cfg['HTMLtableCells.'][$cc . '.']['stdWrap.']
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
3757
                                        : $cfg['HTMLtableCells.']['default.']['stdWrap.'];
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
3758
                                    if (is_array($stdWrap)) {
3759
                                        $colParts[$kkk] = $this->stdWrap($colParts[$kkk], $stdWrap);
3760
                                    }
3761
                                    $colParts[$kkk] = $tag . $colParts[$kkk] . '</' . $tagName . '>';
3762
                                }
3763
                            }
3764
                            $rowParts[$kk] = implode('', $colParts);
3765
                        }
3766
                    }
3767
                    $parts[$k] = implode('', $rowParts);
3768
                }
3769
                if (is_array($cfg['stdWrap.'])) {
3770
                    $parts[$k] = $this->stdWrap($parts[$k], $cfg['stdWrap.']);
3771
                }
3772
            } else {
3773
                $parts[$k] = $this->_parseFunc($parts[$k], $conf);
3774
            }
3775
        }
3776
        return implode('', $parts);
3777
    }
3778
3779
    /**
3780
     * Helper function for parseFunc()
3781
     *
3782
     * @param string $theValue The value to process.
3783
     * @param array $conf TypoScript configuration for parseFunc
3784
     * @return string The processed value
3785
     * @internal
3786
     * @see parseFunc()
3787
     */
3788
    public function _parseFunc($theValue, $conf)
3789
    {
3790
        if (!empty($conf['if.']) && !$this->checkIf($conf['if.'])) {
3791
            return $theValue;
3792
        }
3793
        // Indicates that the data is from within a tag.
3794
        $inside = false;
3795
        // Pointer to the total string position
3796
        $pointer = 0;
3797
        // Loaded with the current typo-tag if any.
3798
        $currentTag = null;
3799
        $stripNL = 0;
3800
        $contentAccum = [];
3801
        $contentAccumP = 0;
3802
        $allowTags = strtolower(str_replace(' ', '', $conf['allowTags'] ?? ''));
3803
        $denyTags = strtolower(str_replace(' ', '', $conf['denyTags'] ?? ''));
3804
        $totalLen = strlen($theValue);
3805
        do {
3806
            if (!$inside) {
3807
                if ($currentTag === null) {
3808
                    // These operations should only be performed on code outside the typotags...
3809
                    // data: this checks that we enter tags ONLY if the first char in the tag is alphanumeric OR '/'
3810
                    $len_p = 0;
3811
                    $c = 100;
3812
                    do {
3813
                        $len = strcspn(substr($theValue, $pointer + $len_p), '<');
3814
                        $len_p += $len + 1;
3815
                        $endChar = ord(strtolower(substr($theValue, $pointer + $len_p, 1)));
3816
                        $c--;
3817
                    } while ($c > 0 && $endChar && ($endChar < 97 || $endChar > 122) && $endChar != 47);
3818
                    $len = $len_p - 1;
3819
                } else {
3820
                    $len = $this->getContentLengthOfCurrentTag($theValue, $pointer, $currentTag[0]);
3821
                }
3822
                // $data is the content until the next <tag-start or end is detected.
3823
                // In case of a currentTag set, this would mean all data between the start- and end-tags
3824
                $data = substr($theValue, $pointer, $len);
3825
                if ($data !== false) {
3826
                    if ($stripNL) {
3827
                        // If the previous tag was set to strip NewLines in the beginning of the next data-chunk.
3828
                        $data = preg_replace('/^[ ]*' . CR . '?' . LF . '/', '', $data);
3829
                    }
3830
                    // These operations should only be performed on code outside the tags...
3831
                    if (!is_array($currentTag)) {
3832
                        // Constants
3833
                        $tsfe = $this->getTypoScriptFrontendController();
3834
                        $tmpConstants = $tsfe->tmpl->setup['constants.'] ?? null;
3835
                        if (!empty($conf['constants']) && is_array($tmpConstants)) {
3836
                            foreach ($tmpConstants as $key => $val) {
3837
                                if (is_string($val)) {
3838
                                    $data = str_replace('###' . $key . '###', $val, $data);
3839
                                }
3840
                            }
3841
                        }
3842
                        // Short
3843
                        if (isset($conf['short.']) && is_array($conf['short.'])) {
3844
                            $shortWords = $conf['short.'];
3845
                            krsort($shortWords);
3846
                            foreach ($shortWords as $key => $val) {
3847
                                if (is_string($val)) {
3848
                                    $data = str_replace($key, $val, $data);
3849
                                }
3850
                            }
3851
                        }
3852
                        // stdWrap
3853
                        if (isset($conf['plainTextStdWrap.']) && is_array($conf['plainTextStdWrap.'])) {
3854
                            $data = $this->stdWrap($data, $conf['plainTextStdWrap.']);
3855
                        }
3856
                        // userFunc
3857
                        if ($conf['userFunc'] ?? false) {
3858
                            $data = $this->callUserFunction($conf['userFunc'], $conf['userFunc.'], $data);
3859
                        }
3860
                        // Makelinks: (Before search-words as we need the links to be generated when searchwords go on...!)
3861
                        if ($conf['makelinks'] ?? false) {
3862
                            $data = $this->http_makelinks($data, $conf['makelinks.']['http.']);
3863
                            $data = $this->mailto_makelinks($data, $conf['makelinks.']['mailto.'] ?? []);
3864
                        }
3865
                        // Search Words:
3866
                        if ($tsfe->no_cache && $conf['sword'] && is_array($tsfe->sWordList) && $tsfe->sWordRegEx) {
3867
                            $newstring = '';
3868
                            do {
3869
                                $pregSplitMode = 'i';
3870
                                if (isset($tsfe->config['config']['sword_noMixedCase']) && !empty($tsfe->config['config']['sword_noMixedCase'])) {
3871
                                    $pregSplitMode = '';
3872
                                }
3873
                                $pieces = preg_split('/' . $tsfe->sWordRegEx . '/' . $pregSplitMode, $data, 2);
3874
                                $newstring .= $pieces[0];
3875
                                $match_len = strlen($data) - (strlen($pieces[0]) + strlen($pieces[1]));
3876
                                $inTag = false;
3877
                                if (strpos($pieces[0], '<') !== false || strpos($pieces[0], '>') !== false) {
3878
                                    // Returns TRUE, if a '<' is closer to the string-end than '>'.
3879
                                    // This is the case if we're INSIDE a tag (that could have been
3880
                                    // made by makelinks...) and we must secure, that the inside of a tag is
3881
                                    // not marked up.
3882
                                    $inTag = strrpos($pieces[0], '<') > strrpos($pieces[0], '>');
3883
                                }
3884
                                // The searchword:
3885
                                $match = substr($data, strlen($pieces[0]), $match_len);
3886
                                if (trim($match) && strlen($match) > 1 && !$inTag) {
3887
                                    $match = $this->wrap($match, $conf['sword']);
3888
                                }
3889
                                // Concatenate the Search Word again.
3890
                                $newstring .= $match;
3891
                                $data = $pieces[1];
3892
                            } while ($pieces[1]);
3893
                            $data = $newstring;
3894
                        }
3895
                    }
3896
                    // Search for tags to process in current data and
3897
                    // call this method recursively if found
3898
                    if (strpos($data, '<') !== false) {
3899
                        foreach ($conf['tags.'] as $tag => $tagConfig) {
3900
                            if (strpos($data, '<' . $tag) !== false) {
3901
                                $data = $this->_parseFunc($data, $conf);
3902
                                break;
3903
                            }
3904
                        }
3905
                    }
3906
                    $contentAccum[$contentAccumP] = isset($contentAccum[$contentAccumP])
3907
                        ? $contentAccum[$contentAccumP] . $data
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
3908
                        : $data;
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
3909
                }
3910
                $inside = true;
3911
            } else {
3912
                // tags
3913
                $len = strcspn(substr($theValue, $pointer), '>') + 1;
3914
                $data = substr($theValue, $pointer, $len);
3915
                if (StringUtility::endsWith($data, '/>') && strpos($data, '<link ') !== 0) {
3916
                    $tagContent = substr($data, 1, -2);
3917
                } else {
3918
                    $tagContent = substr($data, 1, -1);
3919
                }
3920
                $tag = explode(' ', trim($tagContent), 2);
3921
                $tag[0] = strtolower($tag[0]);
3922
                // end tag like </li>
3923
                if ($tag[0][0] === '/') {
3924
                    $tag[0] = substr($tag[0], 1);
3925
                    $tag['out'] = 1;
3926
                }
3927
                if ($conf['tags.'][$tag[0]] ?? false) {
3928
                    $treated = false;
3929
                    $stripNL = false;
3930
                    // in-tag
3931
                    if (!$currentTag && (!isset($tag['out']) || !$tag['out'])) {
3932
                        // $currentTag (array!) is the tag we are currently processing
3933
                        $currentTag = $tag;
3934
                        $contentAccumP++;
3935
                        $treated = true;
3936
                        // in-out-tag: img and other empty tags
3937
                        if (preg_match('/^(area|base|br|col|hr|img|input|meta|param)$/i', $tag[0])) {
3938
                            $tag['out'] = 1;
3939
                        }
3940
                    }
3941
                    // out-tag
3942
                    if ($currentTag[0] === $tag[0] && isset($tag['out']) && $tag['out']) {
3943
                        $theName = $conf['tags.'][$tag[0]];
3944
                        $theConf = $conf['tags.'][$tag[0] . '.'];
3945
                        // This flag indicates, that NL- (13-10-chars) should be stripped first and last.
3946
                        $stripNL = (bool)($theConf['stripNL'] ?? false);
3947
                        // This flag indicates, that this TypoTag section should NOT be included in the nonTypoTag content.
3948
                        $breakOut = (bool)($theConf['breakoutTypoTagContent'] ?? false);
3949
                        $this->parameters = [];
3950
                        if (isset($currentTag[1])) {
3951
                            $params = GeneralUtility::get_tag_attributes($currentTag[1]);
3952
                            if (is_array($params)) {
3953
                                foreach ($params as $option => $val) {
3954
                                    $this->parameters[strtolower($option)] = $val;
3955
                                }
3956
                            }
3957
                            $this->parameters['allParams'] = trim($currentTag[1]);
3958
                        }
3959
                        // Removes NL in the beginning and end of the tag-content AND at the end of the currentTagBuffer.
3960
                        // $stripNL depends on the configuration of the current tag
3961
                        if ($stripNL) {
3962
                            $contentAccum[$contentAccumP - 1] = preg_replace('/' . CR . '?' . LF . '[ ]*$/', '', $contentAccum[$contentAccumP - 1]);
3963
                            $contentAccum[$contentAccumP] = preg_replace('/^[ ]*' . CR . '?' . LF . '/', '', $contentAccum[$contentAccumP]);
3964
                            $contentAccum[$contentAccumP] = preg_replace('/' . CR . '?' . LF . '[ ]*$/', '', $contentAccum[$contentAccumP]);
3965
                        }
3966
                        $this->data[$this->currentValKey] = $contentAccum[$contentAccumP];
3967
                        $newInput = $this->cObjGetSingle($theName, $theConf, '/parseFunc/.tags.' . $tag[0]);
3968
                        // fetch the content object
3969
                        $contentAccum[$contentAccumP] = $newInput;
3970
                        $contentAccumP++;
3971
                        // If the TypoTag section
3972
                        if (!$breakOut) {
3973
                            if (!isset($contentAccum[$contentAccumP - 2])) {
3974
                                $contentAccum[$contentAccumP - 2] = '';
3975
                            }
3976
                            $contentAccum[$contentAccumP - 2] .= ($contentAccum[$contentAccumP - 1] ?? '') . ($contentAccum[$contentAccumP] ?? '');
3977
                            unset($contentAccum[$contentAccumP]);
3978
                            unset($contentAccum[$contentAccumP - 1]);
3979
                            $contentAccumP -= 2;
3980
                        }
3981
                        $currentTag = null;
3982
                        $treated = true;
3983
                    }
3984
                    // other tags
3985
                    if (!$treated) {
3986
                        $contentAccum[$contentAccumP] .= $data;
3987
                    }
3988
                } else {
3989
                    // If a tag was not a typo tag, then it is just added to the content
3990
                    $stripNL = false;
3991
                    if (GeneralUtility::inList($allowTags, $tag[0]) ||
3992
                        ($denyTags !== '*' && !GeneralUtility::inList($denyTags, $tag[0]))) {
3993
                        $contentAccum[$contentAccumP] = isset($contentAccum[$contentAccumP])
3994
                            ? $contentAccum[$contentAccumP] . $data
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
3995
                            : $data;
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
3996
                    } else {
3997
                        $contentAccum[$contentAccumP] = isset($contentAccum[$contentAccumP])
3998
                            ? $contentAccum[$contentAccumP] . htmlspecialchars($data)
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
3999
                            : htmlspecialchars($data);
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
4000
                    }
4001
                }
4002
                $inside = false;
4003
            }
4004
            $pointer += $len;
4005
        } while ($pointer < $totalLen);
4006
        // Parsing nonTypoTag content (all even keys):
4007
        reset($contentAccum);
4008
        $contentAccumCount = count($contentAccum);
4009
        for ($a = 0; $a < $contentAccumCount; $a++) {
4010
            if ($a % 2 != 1) {
4011
                // stdWrap
4012
                if (isset($conf['nonTypoTagStdWrap.']) && is_array($conf['nonTypoTagStdWrap.'])) {
4013
                    $contentAccum[$a] = $this->stdWrap($contentAccum[$a], $conf['nonTypoTagStdWrap.']);
4014
                }
4015
                // userFunc
4016
                if (!empty($conf['nonTypoTagUserFunc'])) {
4017
                    $contentAccum[$a] = $this->callUserFunction($conf['nonTypoTagUserFunc'], $conf['nonTypoTagUserFunc.'], $contentAccum[$a]);
4018
                }
4019
            }
4020
        }
4021
        return implode('', $contentAccum);
4022
    }
4023
4024
    /**
4025
     * Lets you split the content by LF and process each line independently. Used to format content made with the RTE.
4026
     *
4027
     * @param string $theValue The input value
4028
     * @param array $conf TypoScript options
4029
     * @return string The processed input value being returned; Splitted lines imploded by LF again.
4030
     * @internal
4031
     */
4032
    public function encaps_lineSplit($theValue, $conf)
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::encaps_lineSplit" is not in camel caps format
Loading history...
4033
    {
4034
        if ((string)$theValue === '') {
4035
            return '';
4036
        }
4037
        $lParts = explode(LF, $theValue);
4038
4039
        // When the last element is an empty linebreak we need to remove it, otherwise we will have a duplicate empty line.
4040
        $lastPartIndex = count($lParts) - 1;
4041
        if ($lParts[$lastPartIndex] === '' && trim($lParts[$lastPartIndex - 1], CR) === '') {
4042
            array_pop($lParts);
4043
        }
4044
4045
        $encapTags = GeneralUtility::trimExplode(',', strtolower($conf['encapsTagList']), true);
4046
        $nonWrappedTag = $conf['nonWrappedTag'];
4047
        $defaultAlign = isset($conf['defaultAlign.'])
4048
            ? trim($this->stdWrap($conf['defaultAlign'] ?? '', $conf['defaultAlign.']))
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
4049
            : trim($conf['defaultAlign'] ?? '');
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
4050
4051
        $str_content = '';
4052
        foreach ($lParts as $k => $l) {
4053
            $sameBeginEnd = 0;
4054
            $emptyTag = false;
4055
            $l = trim($l);
4056
            $attrib = [];
4057
            $nonWrapped = false;
4058
            $tagName = '';
4059
            if (isset($l[0]) && $l[0] === '<' && substr($l, -1) === '>') {
4060
                $fwParts = explode('>', substr($l, 1), 2);
4061
                [$tagName] = explode(' ', $fwParts[0], 2);
4062
                if (!$fwParts[1]) {
4063
                    if (substr($tagName, -1) === '/') {
4064
                        $tagName = substr($tagName, 0, -1);
4065
                    }
4066
                    if (substr($fwParts[0], -1) === '/') {
4067
                        $sameBeginEnd = 1;
4068
                        $emptyTag = true;
4069
                        $attrib = GeneralUtility::get_tag_attributes('<' . substr($fwParts[0], 0, -1) . '>');
4070
                    }
4071
                } else {
4072
                    $backParts = GeneralUtility::revExplode('<', substr($fwParts[1], 0, -1), 2);
4073
                    $attrib = GeneralUtility::get_tag_attributes('<' . $fwParts[0] . '>');
4074
                    $str_content = $backParts[0];
4075
                    $sameBeginEnd = substr(strtolower($backParts[1]), 1, strlen($tagName)) === strtolower($tagName);
4076
                }
4077
            }
4078
            if ($sameBeginEnd && in_array(strtolower($tagName), $encapTags)) {
4079
                $uTagName = strtoupper($tagName);
4080
                $uTagName = strtoupper($conf['remapTag.'][$uTagName] ?? $uTagName);
4081
            } else {
4082
                $uTagName = strtoupper($nonWrappedTag);
4083
                // The line will be wrapped: $uTagName should not be an empty tag
4084
                $emptyTag = false;
4085
                $str_content = $lParts[$k];
4086
                $nonWrapped = true;
4087
                $attrib = [];
4088
            }
4089
            // Wrapping all inner-content:
4090
            if (is_array($conf['innerStdWrap_all.'])) {
4091
                $str_content = $this->stdWrap($str_content, $conf['innerStdWrap_all.']);
4092
            }
4093
            if ($uTagName) {
4094
                // Setting common attributes
4095
                if (isset($conf['addAttributes.'][$uTagName . '.']) && is_array($conf['addAttributes.'][$uTagName . '.'])) {
4096
                    foreach ($conf['addAttributes.'][$uTagName . '.'] as $kk => $vv) {
4097
                        if (!is_array($vv)) {
4098
                            if ((string)$conf['addAttributes.'][$uTagName . '.'][$kk . '.']['setOnly'] === 'blank') {
4099
                                if ((string)($attrib[$kk] ?? '') === '') {
4100
                                    $attrib[$kk] = $vv;
4101
                                }
4102
                            } elseif ((string)$conf['addAttributes.'][$uTagName . '.'][$kk . '.']['setOnly'] === 'exists') {
4103
                                if (!isset($attrib[$kk])) {
4104
                                    $attrib[$kk] = $vv;
4105
                                }
4106
                            } else {
4107
                                $attrib[$kk] = $vv;
4108
                            }
4109
                        }
4110
                    }
4111
                }
4112
                // Wrapping all inner-content:
4113
                if (isset($conf['encapsLinesStdWrap.'][$uTagName . '.']) && is_array($conf['encapsLinesStdWrap.'][$uTagName . '.'])) {
4114
                    $str_content = $this->stdWrap($str_content, $conf['encapsLinesStdWrap.'][$uTagName . '.']);
4115
                }
4116
                // Default align
4117
                if ((!isset($attrib['align']) || !$attrib['align']) && $defaultAlign) {
4118
                    $attrib['align'] = $defaultAlign;
4119
                }
4120
                $params = GeneralUtility::implodeAttributes($attrib, true);
4121
                if (!isset($conf['removeWrapping']) || !$conf['removeWrapping'] || ($emptyTag && $conf['removeWrapping.']['keepSingleTag'])) {
4122
                    $selfClosingTagList = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
4123
                    if ($emptyTag && in_array(strtolower($uTagName), $selfClosingTagList, true)) {
4124
                        $str_content = '<' . strtolower($uTagName) . (trim($params) ? ' ' . trim($params) : '') . ' />';
4125
                    } else {
4126
                        $str_content = '<' . strtolower($uTagName) . (trim($params) ? ' ' . trim($params) : '') . '>' . $str_content . '</' . strtolower($uTagName) . '>';
4127
                    }
4128
                }
4129
            }
4130
            if ($nonWrapped && isset($conf['wrapNonWrappedLines']) && $conf['wrapNonWrappedLines']) {
4131
                $str_content = $this->wrap($str_content, $conf['wrapNonWrappedLines']);
4132
            }
4133
            $lParts[$k] = $str_content;
4134
        }
4135
        return implode(LF, $lParts);
4136
    }
4137
4138
    /**
4139
     * Finds URLS in text and makes it to a real link.
4140
     * Will find all strings prefixed with "http://" and "https://" in the $data string and make them into a link,
4141
     * linking to the URL we should have found.
4142
     *
4143
     * @param string $data The string in which to search for "http://
4144
     * @param array $conf Configuration for makeLinks, see link
4145
     * @return string The processed input string, being returned.
4146
     * @see _parseFunc()
4147
     */
4148
    public function http_makelinks($data, $conf)
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::http_makelinks" is not in camel caps format
Loading history...
4149
    {
4150
        $parts = [];
4151
        $aTagParams = $this->getATagParams($conf);
4152
        $textstr = '';
4153
        foreach (['http://', 'https://'] as $scheme) {
4154
            $textpieces = explode($scheme, $data);
4155
            $pieces = count($textpieces);
4156
            $textstr = $textpieces[0];
4157
            for ($i = 1; $i < $pieces; $i++) {
4158
                $len = strcspn($textpieces[$i], chr(32) . "\t" . CRLF);
4159
                if (trim(substr($textstr, -1)) === '' && $len) {
4160
                    $lastChar = substr($textpieces[$i], $len - 1, 1);
4161
                    if (!preg_match('/[A-Za-z0-9\\/#_-]/', $lastChar)) {
4162
                        $len--;
4163
                    }
4164
                    // Included '\/' 3/12
4165
                    $parts[0] = substr($textpieces[$i], 0, $len);
4166
                    $parts[1] = substr($textpieces[$i], $len);
4167
                    $keep = $conf['keep'];
4168
                    $linkParts = parse_url($scheme . $parts[0]);
4169
                    $linktxt = '';
4170
                    if (strpos($keep, 'scheme') !== false) {
4171
                        $linktxt = $scheme;
4172
                    }
4173
                    $linktxt .= $linkParts['host'];
4174
                    if (strpos($keep, 'path') !== false) {
4175
                        $linktxt .= $linkParts['path'];
4176
                        // Added $linkParts['query'] 3/12
4177
                        if (strpos($keep, 'query') !== false && $linkParts['query']) {
4178
                            $linktxt .= '?' . $linkParts['query'];
4179
                        } elseif ($linkParts['path'] === '/') {
4180
                            $linktxt = substr($linktxt, 0, -1);
4181
                        }
4182
                    }
4183
                    if (isset($conf['extTarget'])) {
4184
                        if (isset($conf['extTarget.'])) {
4185
                            $target = $this->stdWrap($conf['extTarget'], $conf['extTarget.']);
4186
                        } else {
4187
                            $target = $conf['extTarget'];
4188
                        }
4189
                    } else {
4190
                        $target = $this->getTypoScriptFrontendController()->extTarget;
4191
                    }
4192
4193
                    // check for jump URLs or similar
4194
                    $linkUrl = $this->processUrl(UrlProcessorInterface::CONTEXT_COMMON, $scheme . $parts[0], $conf);
4195
4196
                    $res = '<a href="' . htmlspecialchars($linkUrl) . '"'
4197
                        . ($target !== '' ? ' target="' . htmlspecialchars($target) . '"' : '')
4198
                        . $aTagParams . $this->extLinkATagParams('http://' . $parts[0], 'url') . '>';
4199
4200
                    $wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
4201
                    if ((string)$conf['ATagBeforeWrap'] !== '') {
4202
                        $res = $res . $this->wrap($linktxt, $wrap) . '</a>';
4203
                    } else {
4204
                        $res = $this->wrap($res . $linktxt . '</a>', $wrap);
4205
                    }
4206
                    $textstr .= $res . $parts[1];
4207
                } else {
4208
                    $textstr .= $scheme . $textpieces[$i];
4209
                }
4210
            }
4211
            $data = $textstr;
4212
        }
4213
        return $textstr;
4214
    }
4215
4216
    /**
4217
     * Will find all strings prefixed with "mailto:" in the $data string and make them into a link,
4218
     * linking to the email address they point to.
4219
     *
4220
     * @param string $data The string in which to search for "mailto:
4221
     * @param array $conf Configuration for makeLinks, see link
4222
     * @return string The processed input string, being returned.
4223
     * @see _parseFunc()
4224
     */
4225
    public function mailto_makelinks($data, $conf)
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::mailto_makelinks" is not in camel caps format
Loading history...
4226
    {
4227
        $parts = [];
4228
        // http-split
4229
        $aTagParams = $this->getATagParams($conf);
4230
        $textpieces = explode('mailto:', $data);
4231
        $pieces = count($textpieces);
4232
        $textstr = $textpieces[0];
4233
        $tsfe = $this->getTypoScriptFrontendController();
4234
        for ($i = 1; $i < $pieces; $i++) {
4235
            $len = strcspn($textpieces[$i], chr(32) . "\t" . CRLF);
4236
            if (trim(substr($textstr, -1)) === '' && $len) {
4237
                $lastChar = substr($textpieces[$i], $len - 1, 1);
4238
                if (!preg_match('/[A-Za-z0-9]/', $lastChar)) {
4239
                    $len--;
4240
                }
4241
                $parts[0] = substr($textpieces[$i], 0, $len);
4242
                $parts[1] = substr($textpieces[$i], $len);
4243
                $linktxt = preg_replace('/\\?.*/', '', $parts[0]);
4244
                [$mailToUrl, $linktxt] = $this->getMailTo($parts[0], $linktxt);
4245
                $mailToUrl = $tsfe->spamProtectEmailAddresses === 'ascii' ? $mailToUrl : htmlspecialchars($mailToUrl);
4246
                $res = '<a href="' . $mailToUrl . '"' . $aTagParams . '>';
4247
                $wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
4248
                if ((string)$conf['ATagBeforeWrap'] !== '') {
4249
                    $res = $res . $this->wrap($linktxt, $wrap) . '</a>';
4250
                } else {
4251
                    $res = $this->wrap($res . $linktxt . '</a>', $wrap);
4252
                }
4253
                $textstr .= $res . $parts[1];
4254
            } else {
4255
                $textstr .= 'mailto:' . $textpieces[$i];
4256
            }
4257
        }
4258
        return $textstr;
4259
    }
4260
4261
    /**
4262
     * Creates and returns a TypoScript "imgResource".
4263
     * The value ($file) can either be a file reference (TypoScript resource) or the string "GIFBUILDER".
4264
     * In the first case a current image is returned, possibly scaled down or otherwise processed.
4265
     * In the latter case a GIFBUILDER image is returned; This means an image is made by TYPO3 from layers of elements as GIFBUILDER defines.
4266
     * In the function IMG_RESOURCE() this function is called like $this->getImgResource($conf['file'], $conf['file.']);
4267
     *
4268
     * Structure of the returned info array:
4269
     *  0 => width
4270
     *  1 => height
4271
     *  2 => file extension
4272
     *  3 => file name
4273
     *  origFile => original file name
4274
     *  origFile_mtime => original file mtime
4275
     *  -- only available if processed via FAL: --
4276
     *  originalFile => original file object
4277
     *  processedFile => processed file object
4278
     *  fileCacheHash => checksum of processed file
4279
     *
4280
     * @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.
4281
     * @param array $fileArray TypoScript properties for the imgResource type
4282
     * @return array|null Returns info-array
4283
     * @see cImage()
4284
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder
4285
     */
4286
    public function getImgResource($file, $fileArray)
4287
    {
4288
        $importedFile = null;
4289
        if (empty($file) && empty($fileArray)) {
4290
            return null;
4291
        }
4292
        if (!is_array($fileArray)) {
0 ignored issues
show
introduced by
The condition is_array($fileArray) is always true.
Loading history...
4293
            $fileArray = (array)$fileArray;
4294
        }
4295
        $imageResource = null;
4296
        if ($file === 'GIFBUILDER') {
4297
            $gifCreator = GeneralUtility::makeInstance(GifBuilder::class);
4298
            $theImage = '';
4299
            if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib']) {
4300
                $gifCreator->start($fileArray, $this->data);
4301
                $theImage = $gifCreator->gifBuild();
4302
            }
4303
            $imageResource = $gifCreator->getImageDimensions($theImage);
4304
            $imageResource['origFile'] = $theImage;
4305
        } else {
4306
            if ($file instanceof File) {
4307
                $fileObject = $file;
4308
            } elseif ($file instanceof FileReference) {
4309
                $fileObject = $file->getOriginalFile();
4310
            } else {
4311
                try {
4312
                    if (isset($fileArray['import.']) && $fileArray['import.']) {
4313
                        $importedFile = trim($this->stdWrap('', $fileArray['import.']));
4314
                        if (!empty($importedFile)) {
4315
                            $file = $importedFile;
4316
                        }
4317
                    }
4318
4319
                    if (MathUtility::canBeInterpretedAsInteger($file)) {
4320
                        $treatIdAsReference = isset($fileArray['treatIdAsReference.']) ? $this->stdWrap($fileArray['treatIdAsReference'], $fileArray['treatIdAsReference.']) : $fileArray['treatIdAsReference'];
4321
                        if (!empty($treatIdAsReference)) {
4322
                            $file = $this->getResourceFactory()->getFileReferenceObject($file);
4323
                            $fileObject = $file->getOriginalFile();
4324
                        } else {
4325
                            $fileObject = $this->getResourceFactory()->getFileObject($file);
4326
                        }
4327
                    } elseif (preg_match('/^(0|[1-9][0-9]*):/', $file)) { // combined identifier
4328
                        $fileObject = $this->getResourceFactory()->retrieveFileOrFolderObject($file);
4329
                    } else {
4330
                        if (isset($importedFile) && !empty($importedFile) && !empty($fileArray['import'])) {
4331
                            $file = $fileArray['import'] . $file;
4332
                        }
4333
                        $fileObject = $this->getResourceFactory()->retrieveFileOrFolderObject($file);
4334
                    }
4335
                } catch (Exception $exception) {
4336
                    $this->logger->warning('The image "' . $file . '" could not be found and won\'t be included in frontend output', ['exception' => $exception]);
4337
                    return null;
4338
                }
4339
            }
4340
            if ($fileObject instanceof File) {
4341
                $processingConfiguration = [];
4342
                $processingConfiguration['width'] = isset($fileArray['width.']) ? $this->stdWrap($fileArray['width'], $fileArray['width.']) : $fileArray['width'];
4343
                $processingConfiguration['height'] = isset($fileArray['height.']) ? $this->stdWrap($fileArray['height'], $fileArray['height.']) : $fileArray['height'];
4344
                $processingConfiguration['fileExtension'] = isset($fileArray['ext.']) ? $this->stdWrap($fileArray['ext'], $fileArray['ext.']) : $fileArray['ext'];
4345
                $processingConfiguration['maxWidth'] = isset($fileArray['maxW.']) ? (int)$this->stdWrap($fileArray['maxW'], $fileArray['maxW.']) : (int)$fileArray['maxW'];
4346
                $processingConfiguration['maxHeight'] = isset($fileArray['maxH.']) ? (int)$this->stdWrap($fileArray['maxH'], $fileArray['maxH.']) : (int)$fileArray['maxH'];
4347
                $processingConfiguration['minWidth'] = isset($fileArray['minW.']) ? (int)$this->stdWrap($fileArray['minW'], $fileArray['minW.']) : (int)$fileArray['minW'];
4348
                $processingConfiguration['minHeight'] = isset($fileArray['minH.']) ? (int)$this->stdWrap($fileArray['minH'], $fileArray['minH.']) : (int)$fileArray['minH'];
4349
                $processingConfiguration['noScale'] = isset($fileArray['noScale.']) ? $this->stdWrap($fileArray['noScale'], $fileArray['noScale.']) : $fileArray['noScale'];
4350
                $processingConfiguration['additionalParameters'] = isset($fileArray['params.']) ? $this->stdWrap($fileArray['params'], $fileArray['params.']) : $fileArray['params'];
4351
                $processingConfiguration['frame'] = isset($fileArray['frame.']) ? (int)$this->stdWrap($fileArray['frame'], $fileArray['frame.']) : (int)$fileArray['frame'];
4352
                if ($file instanceof FileReference) {
4353
                    $processingConfiguration['crop'] = $this->getCropAreaFromFileReference($file, $fileArray);
4354
                } else {
4355
                    $processingConfiguration['crop'] = $this->getCropAreaFromFromTypoScriptSettings($fileObject, $fileArray);
4356
                }
4357
4358
                // Possibility to cancel/force profile extraction
4359
                // see $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand']
4360
                if (isset($fileArray['stripProfile'])) {
4361
                    $processingConfiguration['stripProfile'] = $fileArray['stripProfile'];
4362
                }
4363
                // Check if we can handle this type of file for editing
4364
                if ($fileObject->isImage()) {
4365
                    $maskArray = $fileArray['m.'];
4366
                    // Must render mask images and include in hash-calculating
4367
                    // - otherwise we cannot be sure the filename is unique for the setup!
4368
                    if (is_array($maskArray)) {
4369
                        $mask = $this->getImgResource($maskArray['mask'], $maskArray['mask.']);
4370
                        $bgImg = $this->getImgResource($maskArray['bgImg'], $maskArray['bgImg.']);
4371
                        $bottomImg = $this->getImgResource($maskArray['bottomImg'], $maskArray['bottomImg.']);
4372
                        $bottomImg_mask = $this->getImgResource($maskArray['bottomImg_mask'], $maskArray['bottomImg_mask.']);
4373
4374
                        $processingConfiguration['maskImages']['maskImage'] = $mask['processedFile'];
4375
                        $processingConfiguration['maskImages']['backgroundImage'] = $bgImg['processedFile'];
4376
                        $processingConfiguration['maskImages']['maskBottomImage'] = $bottomImg['processedFile'];
4377
                        $processingConfiguration['maskImages']['maskBottomImageMask'] = $bottomImg_mask['processedFile'];
4378
                    }
4379
                    $processedFileObject = $fileObject->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $processingConfiguration);
4380
                    if ($processedFileObject->isProcessed()) {
4381
                        $imageResource = [
4382
                            0 => (int)$processedFileObject->getProperty('width'),
4383
                            1 => (int)$processedFileObject->getProperty('height'),
4384
                            2 => $processedFileObject->getExtension(),
4385
                            3 => $processedFileObject->getPublicUrl(),
4386
                            'origFile' => $fileObject->getPublicUrl(),
4387
                            'origFile_mtime' => $fileObject->getModificationTime(),
4388
                            // This is needed by \TYPO3\CMS\Frontend\Imaging\GifBuilder,
4389
                            // in order for the setup-array to create a unique filename hash.
4390
                            'originalFile' => $fileObject,
4391
                            'processedFile' => $processedFileObject
4392
                        ];
4393
                    }
4394
                }
4395
            }
4396
        }
4397
        // If image was processed by GIFBUILDER:
4398
        // ($imageResource indicates that it was processed the regular way)
4399
        if (!isset($imageResource)) {
4400
            try {
4401
                $theImage = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize((string)$file);
4402
                $info = GeneralUtility::makeInstance(GifBuilder::class)->imageMagickConvert($theImage, 'WEB');
4403
                $info['origFile'] = $theImage;
4404
                // This is needed by \TYPO3\CMS\Frontend\Imaging\GifBuilder, ln 100ff in order for the setup-array to create a unique filename hash.
4405
                $info['origFile_mtime'] = @filemtime($theImage);
4406
                $imageResource = $info;
4407
            } catch (\TYPO3\CMS\Core\Resource\Exception $e) {
4408
                // do nothing in case the file path is invalid
4409
            }
4410
        }
4411
        // Hook 'getImgResource': Post-processing of image resources
4412
        if (isset($imageResource)) {
4413
            /** @var ContentObjectGetImageResourceHookInterface $hookObject */
4414
            foreach ($this->getGetImgResourceHookObjects() as $hookObject) {
4415
                $imageResource = $hookObject->getImgResourcePostProcess($file, (array)$fileArray, $imageResource, $this);
4416
            }
4417
        }
4418
        return $imageResource;
4419
    }
4420
4421
    /**
4422
     * Returns an ImageManipulation\Area object for the given cropVariant (or 'default')
4423
     * or null if the crop settings or crop area is empty.
4424
     *
4425
     * The cropArea from file reference is used, if not set in TypoScript.
4426
     *
4427
     * Example TypoScript settings:
4428
     * file.crop =
4429
     * OR
4430
     * file.crop = 50,50,100,100
4431
     * OR
4432
     * file.crop.data = file:current:crop
4433
     *
4434
     * @param FileReference $fileReference
4435
     * @param array $fileArray TypoScript properties for the imgResource type
4436
     * @return Area|null
4437
     */
4438
    protected function getCropAreaFromFileReference(FileReference $fileReference, array $fileArray)
4439
    {
4440
        // Use cropping area from file reference if nothing is configured in TypoScript.
4441
        if (!isset($fileArray['crop']) && !isset($fileArray['crop.'])) {
4442
            // Set crop variant from TypoScript settings. If not set, use default.
4443
            $cropVariant = $fileArray['cropVariant'] ?? 'default';
4444
            $fileCropArea = $this->createCropAreaFromJsonString((string)$fileReference->getProperty('crop'), $cropVariant);
4445
            return $fileCropArea->isEmpty() ? null : $fileCropArea->makeAbsoluteBasedOnFile($fileReference);
4446
        }
4447
4448
        return $this->getCropAreaFromFromTypoScriptSettings($fileReference, $fileArray);
4449
    }
4450
4451
    /**
4452
     * Returns an ImageManipulation\Area object for the given cropVariant (or 'default')
4453
     * or null if the crop settings or crop area is empty.
4454
     *
4455
     * @param FileInterface $file
4456
     * @param array $fileArray
4457
     * @return Area|null
4458
     */
4459
    protected function getCropAreaFromFromTypoScriptSettings(FileInterface $file, array $fileArray)
4460
    {
4461
        /** @var Area $cropArea */
4462
        $cropArea = null;
4463
        // Resolve TypoScript configured cropping.
4464
        $cropSettings = isset($fileArray['crop.'])
4465
            ? $this->stdWrap($fileArray['crop'], $fileArray['crop.'])
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
4466
            : ($fileArray['crop'] ?? null);
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
4467
4468
        if (is_string($cropSettings)) {
4469
            // Set crop variant from TypoScript settings. If not set, use default.
4470
            $cropVariant = $fileArray['cropVariant'] ?? 'default';
4471
            // Get cropArea from CropVariantCollection, if cropSettings is a valid json.
4472
            // CropVariantCollection::create does json_decode.
4473
            $jsonCropArea = $this->createCropAreaFromJsonString($cropSettings, $cropVariant);
4474
            $cropArea = $jsonCropArea->isEmpty() ? null : $jsonCropArea->makeAbsoluteBasedOnFile($file);
4475
4476
            // Cropping is configured in TypoScript in the following way: file.crop = 50,50,100,100
4477
            if ($jsonCropArea->isEmpty() && preg_match('/^[0-9]+,[0-9]+,[0-9]+,[0-9]+$/', $cropSettings)) {
4478
                $cropSettings = explode(',', $cropSettings);
4479
                if (count($cropSettings) === 4) {
4480
                    $stringCropArea = GeneralUtility::makeInstance(
4481
                        Area::class,
4482
                        ...$cropSettings
4483
                    );
4484
                    $cropArea = $stringCropArea->isEmpty() ? null : $stringCropArea;
4485
                }
4486
            }
4487
        }
4488
4489
        return $cropArea;
4490
    }
4491
4492
    /**
4493
     * Takes a JSON string and creates CropVariantCollection and fetches the corresponding
4494
     * CropArea for that.
4495
     *
4496
     * @param string $cropSettings
4497
     * @param string $cropVariant
4498
     * @return Area
4499
     */
4500
    protected function createCropAreaFromJsonString(string $cropSettings, string $cropVariant): Area
4501
    {
4502
        return CropVariantCollection::create($cropSettings)->getCropArea($cropVariant);
4503
    }
4504
4505
    /***********************************************
4506
     *
4507
     * Data retrieval etc.
4508
     *
4509
     ***********************************************/
4510
    /**
4511
     * 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.
4512
     *
4513
     * @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)
4514
     * @return string|null
4515
     */
4516
    public function getFieldVal($field)
4517
    {
4518
        if (strpos($field, '//') === false) {
4519
            return $this->data[trim($field)] ?? null;
4520
        }
4521
        $sections = GeneralUtility::trimExplode('//', $field, true);
4522
        foreach ($sections as $k) {
4523
            if ((string)$this->data[$k] !== '') {
4524
                return $this->data[$k];
4525
            }
4526
        }
4527
4528
        return '';
4529
    }
4530
4531
    /**
4532
     * 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.
4533
     *
4534
     * @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)
4535
     * @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.
4536
     * @return string The value fetched
4537
     * @see getFieldVal()
4538
     */
4539
    public function getData($string, $fieldArray = null)
4540
    {
4541
        $tsfe = $this->getTypoScriptFrontendController();
4542
        if (!is_array($fieldArray)) {
4543
            $fieldArray = $tsfe->page;
4544
        }
4545
        $retVal = '';
4546
        $sections = explode('//', $string);
4547
        foreach ($sections as $secKey => $secVal) {
4548
            if ($retVal) {
4549
                break;
4550
            }
4551
            $parts = explode(':', $secVal, 2);
4552
            $type = strtolower(trim($parts[0]));
4553
            $typesWithOutParameters = ['level', 'date', 'current', 'pagelayout'];
4554
            $key = trim($parts[1] ?? '');
4555
            if (($key != '') || in_array($type, $typesWithOutParameters)) {
4556
                switch ($type) {
4557
                    case 'gp':
4558
                        // Merge GET and POST and get $key out of the merged array
4559
                        $getPostArray = GeneralUtility::_GET();
4560
                        ArrayUtility::mergeRecursiveWithOverrule($getPostArray, GeneralUtility::_POST());
4561
                        $retVal = $this->getGlobal($key, $getPostArray);
4562
                        break;
4563
                    case 'tsfe':
4564
                        $retVal = $this->getGlobal('TSFE|' . $key);
4565
                        break;
4566
                    case 'getenv':
4567
                        $retVal = getenv($key);
4568
                        break;
4569
                    case 'getindpenv':
4570
                        $retVal = $this->getEnvironmentVariable($key);
4571
                        break;
4572
                    case 'field':
4573
                        $retVal = $this->getGlobal($key, $fieldArray);
4574
                        break;
4575
                    case 'file':
4576
                        $retVal = $this->getFileDataKey($key);
4577
                        break;
4578
                    case 'parameters':
4579
                        $retVal = $this->parameters[$key];
4580
                        break;
4581
                    case 'register':
4582
                        $retVal = $tsfe->register[$key] ?? null;
4583
                        break;
4584
                    case 'global':
4585
                        $retVal = $this->getGlobal($key);
4586
                        break;
4587
                    case 'level':
4588
                        $retVal = count($tsfe->tmpl->rootLine) - 1;
4589
                        break;
4590
                    case 'leveltitle':
4591
                        $keyParts = GeneralUtility::trimExplode(',', $key);
4592
                        $numericKey = $this->getKey($keyParts[0], $tsfe->tmpl->rootLine);
4593
                        $retVal = $this->rootLineValue($numericKey, 'title', strtolower($keyParts[1] ?? '') === 'slide');
4594
                        break;
4595
                    case 'levelmedia':
4596
                        $keyParts = GeneralUtility::trimExplode(',', $key);
4597
                        $numericKey = $this->getKey($keyParts[0], $tsfe->tmpl->rootLine);
4598
                        $retVal = $this->rootLineValue($numericKey, 'media', strtolower($keyParts[1] ?? '') === 'slide');
4599
                        break;
4600
                    case 'leveluid':
4601
                        $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

4601
                        $numericKey = $this->getKey(/** @scrutinizer ignore-type */ $key, $tsfe->tmpl->rootLine);
Loading history...
4602
                        $retVal = $this->rootLineValue($numericKey, 'uid');
4603
                        break;
4604
                    case 'levelfield':
4605
                        $keyParts = GeneralUtility::trimExplode(',', $key);
4606
                        $numericKey = $this->getKey($keyParts[0], $tsfe->tmpl->rootLine);
4607
                        $retVal = $this->rootLineValue($numericKey, $keyParts[1], strtolower($keyParts[2] ?? '') === 'slide');
4608
                        break;
4609
                    case 'fullrootline':
4610
                        $keyParts = GeneralUtility::trimExplode(',', $key);
4611
                        $fullKey = (int)$keyParts[0] - count($tsfe->tmpl->rootLine) + count($tsfe->rootLine);
4612
                        if ($fullKey >= 0) {
4613
                            $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

4613
                            $retVal = $this->rootLineValue($fullKey, $keyParts[1], /** @scrutinizer ignore-type */ stristr($keyParts[2] ?? '', 'slide'), $tsfe->rootLine);
Loading history...
4614
                        }
4615
                        break;
4616
                    case 'date':
4617
                        if (!$key) {
4618
                            $key = 'd/m Y';
4619
                        }
4620
                        $retVal = date($key, $GLOBALS['EXEC_TIME']);
4621
                        break;
4622
                    case 'page':
4623
                        $retVal = $tsfe->page[$key];
4624
                        break;
4625
                    case 'pagelayout':
4626
                        $retVal = GeneralUtility::makeInstance(PageLayoutResolver::class)
4627
                            ->getLayoutForPage($tsfe->page, $tsfe->rootLine);
4628
                        break;
4629
                    case 'current':
4630
                        $retVal = $this->data[$this->currentValKey] ?? null;
4631
                        break;
4632
                    case 'db':
4633
                        $selectParts = GeneralUtility::trimExplode(':', $key);
4634
                        $db_rec = $tsfe->sys_page->getRawRecord($selectParts[0], $selectParts[1]);
4635
                        if (is_array($db_rec) && $selectParts[2]) {
4636
                            $retVal = $db_rec[$selectParts[2]];
4637
                        }
4638
                        break;
4639
                    case 'lll':
4640
                        $retVal = $tsfe->sL('LLL:' . $key);
4641
                        break;
4642
                    case 'path':
4643
                        try {
4644
                            $retVal = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($key);
4645
                        } catch (\TYPO3\CMS\Core\Resource\Exception $e) {
4646
                            // do nothing in case the file path is invalid
4647
                            $retVal = null;
4648
                        }
4649
                        break;
4650
                    case 'cobj':
4651
                        switch ($key) {
4652
                            case 'parentRecordNumber':
4653
                                $retVal = $this->parentRecordNumber;
4654
                                break;
4655
                        }
4656
                        break;
4657
                    case 'debug':
4658
                        switch ($key) {
4659
                            case 'rootLine':
4660
                                $retVal = DebugUtility::viewArray($tsfe->tmpl->rootLine);
4661
                                break;
4662
                            case 'fullRootLine':
4663
                                $retVal = DebugUtility::viewArray($tsfe->rootLine);
4664
                                break;
4665
                            case 'data':
4666
                                $retVal = DebugUtility::viewArray($this->data);
4667
                                break;
4668
                            case 'register':
4669
                                $retVal = DebugUtility::viewArray($tsfe->register);
4670
                                break;
4671
                            case 'page':
4672
                                $retVal = DebugUtility::viewArray($tsfe->page);
4673
                                break;
4674
                        }
4675
                        break;
4676
                    case 'flexform':
4677
                        $keyParts = GeneralUtility::trimExplode(':', $key, true);
4678
                        if (count($keyParts) === 2 && isset($this->data[$keyParts[0]])) {
4679
                            $flexFormContent = $this->data[$keyParts[0]];
4680
                            if (!empty($flexFormContent)) {
4681
                                $flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
4682
                                $flexFormKey = str_replace('.', '|', $keyParts[1]);
4683
                                $settings = $flexFormService->convertFlexFormContentToArray($flexFormContent);
4684
                                $retVal = $this->getGlobal($flexFormKey, $settings);
4685
                            }
4686
                        }
4687
                        break;
4688
                    case 'session':
4689
                        $keyParts = GeneralUtility::trimExplode('|', $key, true);
4690
                        $sessionKey = array_shift($keyParts);
4691
                        $retVal = $this->getTypoScriptFrontendController()->fe_user->getSessionData($sessionKey);
4692
                        foreach ($keyParts as $keyPart) {
4693
                            if (is_object($retVal)) {
4694
                                $retVal = $retVal->{$keyPart};
4695
                            } elseif (is_array($retVal)) {
4696
                                $retVal = $retVal[$keyPart];
4697
                            } else {
4698
                                $retVal = '';
4699
                                break;
4700
                            }
4701
                        }
4702
                        if (!is_scalar($retVal)) {
4703
                            $retVal = '';
4704
                        }
4705
                        break;
4706
                    case 'context':
4707
                        $context = GeneralUtility::makeInstance(Context::class);
4708
                        [$aspectName, $propertyName] = GeneralUtility::trimExplode(':', $key, true, 2);
4709
                        $retVal = $context->getPropertyFromAspect($aspectName, $propertyName, '');
4710
                        if (is_array($retVal)) {
4711
                            $retVal = implode(',', $retVal);
4712
                        }
4713
                        if (!is_scalar($retVal)) {
4714
                            $retVal = '';
4715
                        }
4716
                        break;
4717
                    case 'site':
4718
                        $site = $this->getTypoScriptFrontendController()->getSite();
4719
                        if ($key === 'identifier') {
4720
                            $retVal = $site->getIdentifier();
4721
                        } elseif ($key === 'base') {
4722
                            $retVal = $site->getBase();
4723
                        } else {
4724
                            try {
4725
                                $retVal = ArrayUtility::getValueByPath($site->getConfiguration(), $key, '.');
4726
                            } catch (MissingArrayPathException $exception) {
4727
                                $this->logger->warning(sprintf('getData() with "%s" failed', $key), ['exception' => $exception]);
4728
                            }
4729
                        }
4730
                        break;
4731
                    case 'sitelanguage':
4732
                        $siteLanguage = $this->getTypoScriptFrontendController()->getLanguage();
4733
                        $config = $siteLanguage->toArray();
4734
                        if (isset($config[$key])) {
4735
                            $retVal = $config[$key];
4736
                        }
4737
                        break;
4738
                }
4739
            }
4740
4741
            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getData'] ?? [] as $className) {
4742
                $hookObject = GeneralUtility::makeInstance($className);
4743
                if (!$hookObject instanceof ContentObjectGetDataHookInterface) {
4744
                    throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetDataHookInterface::class, 1195044480);
4745
                }
4746
                $ref = $this; // introduced for phpstan to not lose type information when passing $this into callUserFunction
4747
                $retVal = $hookObject->getDataExtension($string, $fieldArray, $secVal, $retVal, $ref);
4748
            }
4749
        }
4750
        return $retVal;
4751
    }
4752
4753
    /**
4754
     * Gets file information. This is a helper function for the getData() method above, which resolves e.g.
4755
     * page.10.data = file:current:title
4756
     * or
4757
     * page.10.data = file:17:title
4758
     *
4759
     * @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.)
4760
     * @return string|int The value as retrieved from the file object.
4761
     */
4762
    protected function getFileDataKey($key)
4763
    {
4764
        [$fileUidOrCurrentKeyword, $requestedFileInformationKey] = explode(':', $key, 3);
4765
        try {
4766
            if ($fileUidOrCurrentKeyword === 'current') {
4767
                $fileObject = $this->getCurrentFile();
4768
            } elseif (MathUtility::canBeInterpretedAsInteger($fileUidOrCurrentKeyword)) {
4769
                /** @var ResourceFactory $fileFactory */
4770
                $fileFactory = GeneralUtility::makeInstance(ResourceFactory::class);
4771
                $fileObject = $fileFactory->getFileObject($fileUidOrCurrentKeyword);
0 ignored issues
show
Bug introduced by
$fileUidOrCurrentKeyword 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

4771
                $fileObject = $fileFactory->getFileObject(/** @scrutinizer ignore-type */ $fileUidOrCurrentKeyword);
Loading history...
4772
            } else {
4773
                $fileObject = null;
4774
            }
4775
        } catch (Exception $exception) {
4776
            $this->logger->warning('The file "' . $fileUidOrCurrentKeyword . '" could not be found and won\'t be included in frontend output', ['exception' => $exception]);
4777
            $fileObject = null;
4778
        }
4779
4780
        if ($fileObject instanceof FileInterface) {
4781
            // All properties of the \TYPO3\CMS\Core\Resource\FileInterface are available here:
4782
            switch ($requestedFileInformationKey) {
4783
                case 'name':
4784
                    return $fileObject->getName();
4785
                case 'uid':
4786
                    if (method_exists($fileObject, 'getUid')) {
4787
                        return $fileObject->getUid();
4788
                    }
4789
                    return 0;
4790
                case 'originalUid':
4791
                    if ($fileObject instanceof FileReference) {
0 ignored issues
show
introduced by
$fileObject is never a sub-type of TYPO3\CMS\Core\Resource\FileReference.
Loading history...
4792
                        return $fileObject->getOriginalFile()->getUid();
4793
                    }
4794
                    return null;
4795
                case 'size':
4796
                    return $fileObject->getSize();
4797
                case 'sha1':
4798
                    return $fileObject->getSha1();
4799
                case 'extension':
4800
                    return $fileObject->getExtension();
4801
                case 'mimetype':
4802
                    return $fileObject->getMimeType();
4803
                case 'contents':
4804
                    return $fileObject->getContents();
4805
                case 'publicUrl':
4806
                    return $fileObject->getPublicUrl();
4807
                default:
4808
                    // Generic alternative here
4809
                    return $fileObject->getProperty($requestedFileInformationKey);
4810
            }
4811
        } else {
4812
            // @todo fail silently as is common in tslib_content
4813
            return 'Error: no file object';
4814
        }
4815
    }
4816
4817
    /**
4818
     * Returns a value from the current rootline (site) from $GLOBALS['TSFE']->tmpl->rootLine;
4819
     *
4820
     * @param string $key Which level in the root line
4821
     * @param string $field The field in the rootline record to return (a field from the pages table)
4822
     * @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
4823
     * @param mixed $altRootLine If you supply an array for this it will be used as an alternative root line array
4824
     * @return string The value from the field of the rootline.
4825
     * @internal
4826
     * @see getData()
4827
     */
4828
    public function rootLineValue($key, $field, $slideBack = false, $altRootLine = '')
4829
    {
4830
        $rootLine = is_array($altRootLine) ? $altRootLine : $this->getTypoScriptFrontendController()->tmpl->rootLine;
4831
        if (!$slideBack) {
4832
            return $rootLine[$key][$field];
4833
        }
4834
        for ($a = $key; $a >= 0; $a--) {
4835
            $val = $rootLine[$a][$field];
4836
            if ($val) {
4837
                return $val;
4838
            }
4839
        }
4840
4841
        return '';
4842
    }
4843
4844
    /**
4845
     * Return global variable where the input string $var defines array keys separated by "|"
4846
     * Example: $var = "HTTP_SERVER_VARS | something" will return the value $GLOBALS['HTTP_SERVER_VARS']['something'] value
4847
     *
4848
     * @param string $keyString Global var key, eg. "HTTP_GET_VAR" or "HTTP_GET_VARS|id" to get the GET parameter "id" back.
4849
     * @param array $source Alternative array than $GLOBAL to get variables from.
4850
     * @return mixed Whatever value. If none, then blank string.
4851
     * @see getData()
4852
     */
4853
    public function getGlobal($keyString, $source = null)
4854
    {
4855
        $keys = explode('|', $keyString);
4856
        $numberOfLevels = count($keys);
4857
        $rootKey = trim($keys[0]);
4858
        $value = isset($source) ? $source[$rootKey] : $GLOBALS[$rootKey];
4859
        for ($i = 1; $i < $numberOfLevels && isset($value); $i++) {
4860
            $currentKey = trim($keys[$i]);
4861
            if (is_object($value)) {
4862
                $value = $value->{$currentKey};
4863
            } elseif (is_array($value)) {
4864
                $value = $value[$currentKey];
4865
            } else {
4866
                $value = '';
4867
                break;
4868
            }
4869
        }
4870
        if (!is_scalar($value)) {
4871
            $value = '';
4872
        }
4873
        return $value;
4874
    }
4875
4876
    /**
4877
     * 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).
4878
     * Example: entrylevel = -1 means that entryLevel ends up pointing at the outermost-level, -2 means the level before the outermost...
4879
     *
4880
     * @param int $key The integer to transform
4881
     * @param array $arr array in which the key should be found.
4882
     * @return int The processed integer key value.
4883
     * @internal
4884
     * @see getData()
4885
     */
4886
    public function getKey($key, $arr)
4887
    {
4888
        $key = (int)$key;
4889
        if (is_array($arr)) {
0 ignored issues
show
introduced by
The condition is_array($arr) is always true.
Loading history...
4890
            if ($key < 0) {
4891
                $key = count($arr) + $key;
4892
            }
4893
            if ($key < 0) {
4894
                $key = 0;
4895
            }
4896
        }
4897
        return $key;
4898
    }
4899
4900
    /***********************************************
4901
     *
4902
     * Link functions (typolink)
4903
     *
4904
     ***********************************************/
4905
4906
    /**
4907
     * called from the typoLink() function
4908
     *
4909
     * does the magic to split the full "typolink" string like "15,13 _blank myclass &more=1"
4910
     * into separate parts
4911
     *
4912
     * @param string $linkText The string (text) to link
4913
     * @param string $mixedLinkParameter destination data like "15,13 _blank myclass &more=1" used to create the link
4914
     * @param array $configuration TypoScript configuration
4915
     * @return array|string
4916
     * @see typoLink()
4917
     *
4918
     * @todo the functionality of the "file:" syntax + the hook should be marked as deprecated, an upgrade wizard should handle existing links
4919
     */
4920
    protected function resolveMixedLinkParameter($linkText, $mixedLinkParameter, &$configuration = [])
4921
    {
4922
        $linkParameter = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $linkParameter is dead and can be removed.
Loading history...
4923
4924
        // Link parameter value = first part
4925
        $linkParameterParts = GeneralUtility::makeInstance(TypoLinkCodecService::class)->decode($mixedLinkParameter);
4926
4927
        // Check for link-handler keyword
4928
        $linkHandlerExploded = explode(':', $linkParameterParts['url'], 2);
4929
        $linkHandlerKeyword = $linkHandlerExploded[0] ?? null;
4930
        $linkHandlerValue = $linkHandlerExploded[1] ?? null;
4931
        if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['typolinkLinkHandler'][$linkHandlerKeyword])
4932
            && (string)$linkHandlerValue !== ''
4933
        ) {
4934
            $linkHandlerObj = GeneralUtility::makeInstance($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['typolinkLinkHandler'][$linkHandlerKeyword]);
4935
            if (method_exists($linkHandlerObj, 'main')) {
4936
                return $linkHandlerObj->main($linkText, $configuration, $linkHandlerKeyword, $linkHandlerValue, $mixedLinkParameter, $this);
4937
            }
4938
        }
4939
4940
        if (in_array(strtolower(preg_replace('#\s|[[:cntrl:]]#', '', $linkHandlerKeyword)), ['javascript', 'data'], true)) {
4941
            // Disallow insecure scheme's like javascript: or data:
4942
            return $linkText;
4943
        }
4944
        $linkParameter = $linkParameterParts['url'];
4945
4946
        // additional parameters that need to be set
4947
        if ($linkParameterParts['additionalParams'] !== '') {
4948
            $forceParams = $linkParameterParts['additionalParams'];
4949
            // params value
4950
            $configuration['additionalParams'] .= $forceParams[0] === '&' ? $forceParams : '&' . $forceParams;
4951
        }
4952
4953
        return [
4954
            'href'   => $linkParameter,
4955
            'target' => $linkParameterParts['target'],
4956
            'class'  => $linkParameterParts['class'],
4957
            'title'  => $linkParameterParts['title']
4958
        ];
4959
    }
4960
4961
    /**
4962
     * Implements the "typolink" property of stdWrap (and others)
4963
     * 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.
4964
     * 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.
4965
     * 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.
4966
     * For many more details on the parameters and how they are interpreted, please see the link to TSref below.
4967
     *
4968
     * the FAL API is handled with the namespace/prefix "file:..."
4969
     *
4970
     * @param string $linkText The string (text) to link
4971
     * @param array $conf TypoScript configuration (see link below)
4972
     * @return string A link-wrapped string.
4973
     * @see stdWrap()
4974
     * @see \TYPO3\CMS\Frontend\Plugin\AbstractPlugin::pi_linkTP()
4975
     */
4976
    public function typoLink($linkText, $conf)
4977
    {
4978
        $linkText = (string)$linkText;
4979
        $tsfe = $this->getTypoScriptFrontendController();
4980
4981
        $linkParameter = trim(
4982
            (isset($conf['parameter.']))
4983
            ? $this->stdWrap($conf['parameter'] ?? '', $conf['parameter.'])
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
4984
            : ($conf['parameter'] ?? '')
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
4985
        );
4986
        $this->lastTypoLinkUrl = '';
4987
        $this->lastTypoLinkTarget = '';
4988
4989
        $resolvedLinkParameters = $this->resolveMixedLinkParameter($linkText, $linkParameter, $conf);
4990
        // check if the link handler hook has resolved the link completely already
4991
        if (!is_array($resolvedLinkParameters)) {
4992
            return $resolvedLinkParameters;
4993
        }
4994
        $linkParameter = $resolvedLinkParameters['href'];
4995
        $target = $resolvedLinkParameters['target'];
4996
        $title = $resolvedLinkParameters['title'];
4997
4998
        if (!$linkParameter) {
4999
            return $this->resolveAnchorLink($linkText, $conf ?? []);
5000
        }
5001
5002
        // Detecting kind of link and resolve all necessary parameters
5003
        $linkService = GeneralUtility::makeInstance(LinkService::class);
5004
        try {
5005
            $linkDetails = $linkService->resolve($linkParameter);
5006
        } catch (Exception\InvalidPathException $exception) {
5007
            $this->logger->warning('The link could not be generated', ['exception' => $exception]);
5008
            return $linkText;
5009
        }
5010
5011
        $linkDetails['typoLinkParameter'] = $linkParameter;
5012
        if (isset($linkDetails['type']) && isset($GLOBALS['TYPO3_CONF_VARS']['FE']['typolinkBuilder'][$linkDetails['type']])) {
5013
            /** @var AbstractTypolinkBuilder $linkBuilder */
5014
            $linkBuilder = GeneralUtility::makeInstance(
5015
                $GLOBALS['TYPO3_CONF_VARS']['FE']['typolinkBuilder'][$linkDetails['type']],
5016
                $this,
5017
                $tsfe
5018
            );
5019
            try {
5020
                [$this->lastTypoLinkUrl, $linkText, $target] = $linkBuilder->build($linkDetails, $linkText, $target, $conf);
5021
                $this->lastTypoLinkTarget = htmlspecialchars($target);
5022
                $this->lastTypoLinkLD['target'] = htmlspecialchars($target);
5023
                $this->lastTypoLinkLD['totalUrl'] = $this->lastTypoLinkUrl;
5024
            } catch (UnableToLinkException $e) {
5025
                $this->logger->debug(sprintf('Unable to link "%s": %s', $e->getLinkText(), $e->getMessage()), ['exception' => $e]);
5026
5027
                // Only return the link text directly
5028
                return $e->getLinkText();
5029
            }
5030
        } elseif (isset($linkDetails['url'])) {
5031
            $this->lastTypoLinkUrl = $linkDetails['url'];
5032
            $this->lastTypoLinkTarget = htmlspecialchars($target);
5033
            $this->lastTypoLinkLD['target'] = htmlspecialchars($target);
5034
            $this->lastTypoLinkLD['totalUrl'] = $this->lastTypoLinkUrl;
5035
        } else {
5036
            return $linkText;
5037
        }
5038
5039
        // We need to backup the URL because ATagParams might call typolink again and change the last URL.
5040
        $url = $this->lastTypoLinkUrl;
5041
        $finalTagParts = [
5042
            'aTagParams' => $this->getATagParams($conf) . $this->extLinkATagParams($this->lastTypoLinkUrl, $linkDetails['type']),
5043
            'url'        => $url,
5044
            'TYPE'       => $linkDetails['type']
5045
        ];
5046
5047
        // Ensure "href" is not in the list of aTagParams to avoid double tags, usually happens within buggy parseFunc settings
5048
        if (!empty($finalTagParts['aTagParams'])) {
5049
            $aTagParams = GeneralUtility::get_tag_attributes($finalTagParts['aTagParams']);
5050
            if (isset($aTagParams['href'])) {
5051
                unset($aTagParams['href']);
5052
                $finalTagParts['aTagParams'] = GeneralUtility::implodeAttributes($aTagParams);
5053
            }
5054
        }
5055
5056
        // Building the final <a href=".."> tag
5057
        $tagAttributes = [];
5058
5059
        // Title attribute
5060
        if (empty($title)) {
5061
            $title = $conf['title'] ?? '';
5062
            if (isset($conf['title.']) && is_array($conf['title.'])) {
5063
                $title = $this->stdWrap($title, $conf['title.']);
5064
            }
5065
        }
5066
5067
        // Check, if the target is coded as a JS open window link:
5068
        $JSwindowParts = [];
5069
        $JSwindowParams = '';
5070
        if ($target && preg_match('/^([0-9]+)x([0-9]+)(:(.*)|.*)$/', $target, $JSwindowParts)) {
5071
            // Take all pre-configured and inserted parameters and compile parameter list, including width+height:
5072
            $JSwindow_tempParamsArr = GeneralUtility::trimExplode(',', strtolower($conf['JSwindow_params'] . ',' . $JSwindowParts[4]), true);
5073
            $JSwindow_paramsArr = [];
5074
            foreach ($JSwindow_tempParamsArr as $JSv) {
5075
                [$JSp, $JSv] = explode('=', $JSv, 2);
5076
                $JSwindow_paramsArr[$JSp] = $JSp . '=' . $JSv;
5077
            }
5078
            // Add width/height:
5079
            $JSwindow_paramsArr['width'] = 'width=' . $JSwindowParts[1];
5080
            $JSwindow_paramsArr['height'] = 'height=' . $JSwindowParts[2];
5081
            // Imploding into string:
5082
            $JSwindowParams = implode(',', $JSwindow_paramsArr);
5083
        }
5084
        if (!$JSwindowParams && $linkDetails['type'] === LinkService::TYPE_EMAIL && $tsfe->spamProtectEmailAddresses === 'ascii') {
5085
            $tagAttributes['href'] = $finalTagParts['url'];
5086
        } else {
5087
            $tagAttributes['href'] = htmlspecialchars($finalTagParts['url']);
5088
        }
5089
        if (!empty($title)) {
5090
            $tagAttributes['title'] = htmlspecialchars($title);
5091
        }
5092
5093
        // Target attribute
5094
        if (!empty($target)) {
5095
            $tagAttributes['target'] = htmlspecialchars($target);
5096
        } elseif ($JSwindowParams && !in_array($tsfe->xhtmlDoctype, ['xhtml_strict', 'xhtml_11'], true)) {
5097
            // Create TARGET-attribute only if the right doctype is used
5098
            $tagAttributes['target'] = 'FEopenLink';
5099
        }
5100
5101
        if ($JSwindowParams) {
5102
            $onClick = 'vHWin=window.open(' . GeneralUtility::quoteJSvalue($tsfe->baseUrlWrap($finalTagParts['url'])) . ',\'FEopenLink\',' . GeneralUtility::quoteJSvalue($JSwindowParams) . ');vHWin.focus();return false;';
5103
            $tagAttributes['onclick'] = htmlspecialchars($onClick);
5104
        }
5105
5106
        if (!empty($resolvedLinkParameters['class'])) {
5107
            $tagAttributes['class'] = htmlspecialchars($resolvedLinkParameters['class']);
5108
        }
5109
5110
        // Prevent trouble with double and missing spaces between attributes and merge params before implode
5111
        $finalTagAttributes = array_merge($tagAttributes, GeneralUtility::get_tag_attributes($finalTagParts['aTagParams']));
5112
        $finalTagAttributes = $this->addSecurityRelValues($finalTagAttributes, $target, $tagAttributes['href']);
5113
        $finalAnchorTag = '<a ' . GeneralUtility::implodeAttributes($finalTagAttributes) . '>';
5114
5115
        if (!empty($finalTagParts['aTagParams'])) {
5116
            $tagAttributes = array_merge($tagAttributes, GeneralUtility::get_tag_attributes($finalTagParts['aTagParams']));
5117
        }
5118
        // kept for backwards-compatibility in hooks
5119
        $finalTagParts['targetParams'] = !empty($tagAttributes['target']) ? ' target="' . $tagAttributes['target'] . '"' : '';
5120
        $this->lastTypoLinkTarget = $target;
5121
5122
        // Call user function:
5123
        if ($conf['userFunc'] ?? false) {
5124
            $finalTagParts['TAG'] = $finalAnchorTag;
5125
            $finalAnchorTag = $this->callUserFunction($conf['userFunc'], $conf['userFunc.'], $finalTagParts);
0 ignored issues
show
Bug introduced by
$finalTagParts of type array<string,mixed|string> is incompatible with the type string expected by parameter $content of TYPO3\CMS\Frontend\Conte...rer::callUserFunction(). ( Ignorable by Annotation )

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

5125
            $finalAnchorTag = $this->callUserFunction($conf['userFunc'], $conf['userFunc.'], /** @scrutinizer ignore-type */ $finalTagParts);
Loading history...
5126
        }
5127
5128
        // Hook: Call post processing function for link rendering:
5129
        $_params = [
5130
            'conf' => &$conf,
5131
            'linktxt' => &$linkText,
5132
            'finalTag' => &$finalAnchorTag,
5133
            'finalTagParts' => &$finalTagParts,
5134
            'linkDetails' => &$linkDetails,
5135
            'tagAttributes' => &$tagAttributes
5136
        ];
5137
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['typoLink_PostProc'] ?? [] as $_funcRef) {
5138
            $ref = $this; // introduced for phpstan to not lose type information when passing $this into callUserFunction
5139
            GeneralUtility::callUserFunction($_funcRef, $_params, $ref);
5140
        }
5141
5142
        // If flag "returnLastTypoLinkUrl" set, then just return the latest URL made:
5143
        if ($conf['returnLast'] ?? false) {
5144
            switch ($conf['returnLast']) {
5145
                case 'url':
5146
                    return $this->lastTypoLinkUrl;
5147
                case 'target':
5148
                    return $this->lastTypoLinkTarget;
5149
            }
5150
        }
5151
5152
        $wrap = isset($conf['wrap.'])
5153
            ? $this->stdWrap($conf['wrap'] ?? '', $conf['wrap.'])
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
5154
            : $conf['wrap'] ?? '';
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
5155
5156
        if ($conf['ATagBeforeWrap'] ?? false) {
5157
            return $finalAnchorTag . $this->wrap($linkText, $wrap) . '</a>';
5158
        }
5159
        return $this->wrap($finalAnchorTag . $linkText . '</a>', $wrap);
5160
    }
5161
5162
    protected function addSecurityRelValues(array $tagAttributes, ?string $target, string $url): array
5163
    {
5164
        $relAttribute = 'noreferrer';
5165
        if (in_array($target, ['', null, '_self', '_parent', '_top'], true) || $this->isInternalUrl($url)) {
5166
            return $tagAttributes;
5167
        }
5168
5169
        if (!isset($tagAttributes['rel'])) {
5170
            $tagAttributes['rel'] = $relAttribute;
5171
            return $tagAttributes;
5172
        }
5173
5174
        $tagAttributes['rel'] = implode(' ', array_unique(array_merge(
5175
            GeneralUtility::trimExplode(' ', $relAttribute),
5176
            GeneralUtility::trimExplode(' ', $tagAttributes['rel'])
5177
        )));
5178
5179
        return $tagAttributes;
5180
    }
5181
5182
    /**
5183
     * Checks whether the given url is an internal url.
5184
     *
5185
     * It will check the host part only, against all configured sites
5186
     * whether the given host is any. If so, the url is considered internal
5187
     *
5188
     * @param string $url The url to check.
5189
     * @return bool
5190
     * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
5191
     */
5192
    protected function isInternalUrl(string $url): bool
5193
    {
5194
        $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime');
5195
        $parsedUrl = parse_url($url);
5196
        $foundDomains = 0;
5197
        if (!isset($parsedUrl['host'])) {
5198
            return true;
5199
        }
5200
5201
        $cacheIdentifier = sha1('isInternalDomain' . $parsedUrl['host']);
5202
5203
        if ($cache->has($cacheIdentifier) === false) {
5204
            foreach (GeneralUtility::makeInstance(SiteFinder::class)->getAllSites() as $site) {
5205
                if ($site->getBase()->getHost() === $parsedUrl['host']) {
5206
                    ++$foundDomains;
5207
                    break;
5208
                }
5209
5210
                if ($site->getBase()->getHost() === '' && GeneralUtility::isOnCurrentHost($url)) {
5211
                    ++$foundDomains;
5212
                    break;
5213
                }
5214
            }
5215
5216
            $cache->set($cacheIdentifier, $foundDomains > 0);
5217
        }
5218
5219
        return (bool)$cache->get($cacheIdentifier);
5220
    }
5221
5222
    /**
5223
     * Based on the input "TypoLink" TypoScript configuration this will return the generated URL
5224
     *
5225
     * @param array $conf TypoScript properties for "typolink
5226
     * @return string The URL of the link-tag that typolink() would by itself return
5227
     * @see typoLink()
5228
     */
5229
    public function typoLink_URL($conf)
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::typoLink_URL" is not in camel caps format
Loading history...
5230
    {
5231
        $this->typoLink('|', $conf);
5232
        return $this->lastTypoLinkUrl;
5233
    }
5234
5235
    /**
5236
     * Returns a linked string made from typoLink parameters.
5237
     *
5238
     * 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.
5239
     * Optionally you can supply $urlParameters which is an array with key/value pairs that are rawurlencoded and appended to the resulting url.
5240
     *
5241
     * @param string $label Text string being wrapped by the link.
5242
     * @param string $params Link parameter; eg. "123" for page id, "[email protected]" for email address, "http://...." for URL, "fileadmin/example.txt" for file.
5243
     * @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.
5244
     * @param string $target Specific target set, if any. (Default is using the current)
5245
     * @return string The wrapped $label-text string
5246
     * @see getTypoLink_URL()
5247
     */
5248
    public function getTypoLink($label, $params, $urlParameters = [], $target = '')
5249
    {
5250
        $conf = [];
5251
        $conf['parameter'] = $params;
5252
        if ($target) {
5253
            $conf['target'] = $target;
5254
            $conf['extTarget'] = $target;
5255
            $conf['fileTarget'] = $target;
5256
        }
5257
        if (is_array($urlParameters)) {
5258
            if (!empty($urlParameters)) {
5259
                $conf['additionalParams'] .= HttpUtility::buildQueryString($urlParameters, '&');
5260
            }
5261
        } else {
5262
            $conf['additionalParams'] .= $urlParameters;
5263
        }
5264
        $out = $this->typoLink($label, $conf);
5265
        return $out;
5266
    }
5267
5268
    /**
5269
     * Returns the canonical URL to the current "location", which include the current page ID and type
5270
     * and optionally the query string
5271
     *
5272
     * @param bool $addQueryString Whether additional GET arguments in the query string should be included or not
5273
     * @return string
5274
     */
5275
    public function getUrlToCurrentLocation($addQueryString = true)
5276
    {
5277
        $conf = [];
5278
        $conf['parameter'] = $this->getTypoScriptFrontendController()->id . ',' . $this->getTypoScriptFrontendController()->type;
5279
        if ($addQueryString) {
5280
            $conf['addQueryString'] = '1';
5281
            $linkVars = implode(',', array_keys(GeneralUtility::explodeUrl2Array($this->getTypoScriptFrontendController()->linkVars)));
5282
            $conf['addQueryString.'] = [
5283
                'method' => 'GET',
5284
                'exclude' => 'id,type,cHash' . ($linkVars ? ',' . $linkVars : '')
5285
            ];
5286
        }
5287
5288
        return $this->typoLink_URL($conf);
5289
    }
5290
5291
    /**
5292
     * Returns the URL of a "typolink" create from the input parameter string, url-parameters and target
5293
     *
5294
     * @param string $params Link parameter; eg. "123" for page id, "[email protected]" for email address, "http://...." for URL, "fileadmin/example.txt" for file.
5295
     * @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.
5296
     * @param string $target Specific target set, if any. (Default is using the current)
5297
     * @return string The URL
5298
     * @see getTypoLink()
5299
     */
5300
    public function getTypoLink_URL($params, $urlParameters = [], $target = '')
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::getTypoLink_URL" is not in camel caps format
Loading history...
5301
    {
5302
        $this->getTypoLink('', $params, $urlParameters, $target);
5303
        return $this->lastTypoLinkUrl;
5304
    }
5305
5306
    /**
5307
     * Loops over all configured URL modifier hooks (if available) and returns the generated URL or NULL if no URL was generated.
5308
     *
5309
     * @param string $context The context in which the method is called (e.g. typoLink).
5310
     * @param string $url The URL that should be processed.
5311
     * @param array $typolinkConfiguration The current link configuration array.
5312
     * @return string|null Returns NULL if URL was not processed or the processed URL as a string.
5313
     * @throws \RuntimeException if a hook was registered but did not fulfill the correct parameters.
5314
     */
5315
    protected function processUrl($context, $url, $typolinkConfiguration = [])
5316
    {
5317
        $urlProcessors = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['urlProcessing']['urlProcessors'] ?? [];
5318
        if (empty($urlProcessors)) {
5319
            return $url;
5320
        }
5321
5322
        foreach ($urlProcessors as $identifier => $configuration) {
5323
            if (empty($configuration) || !is_array($configuration)) {
5324
                throw new \RuntimeException('Missing configuration for URI processor "' . $identifier . '".', 1442050529);
5325
            }
5326
            if (!is_string($configuration['processor']) || empty($configuration['processor']) || !class_exists($configuration['processor']) || !is_subclass_of($configuration['processor'], UrlProcessorInterface::class)) {
5327
                throw new \RuntimeException('The URI processor "' . $identifier . '" defines an invalid provider. Ensure the class exists and implements the "' . UrlProcessorInterface::class . '".', 1442050579);
5328
            }
5329
        }
5330
5331
        $orderedProcessors = GeneralUtility::makeInstance(DependencyOrderingService::class)->orderByDependencies($urlProcessors);
5332
        $keepProcessing = true;
5333
5334
        foreach ($orderedProcessors as $configuration) {
5335
            /** @var UrlProcessorInterface $urlProcessor */
5336
            $urlProcessor = GeneralUtility::makeInstance($configuration['processor']);
5337
            $url = $urlProcessor->process($context, $url, $typolinkConfiguration, $this, $keepProcessing);
5338
            if (!$keepProcessing) {
5339
                break;
5340
            }
5341
        }
5342
5343
        return $url;
5344
    }
5345
5346
    /**
5347
     * Creates a href attibute for given $mailAddress.
5348
     * The function uses spamProtectEmailAddresses for encoding the mailto statement.
5349
     * If spamProtectEmailAddresses is disabled, it'll just return a string like "mailto:[email protected]".
5350
     *
5351
     * @param string $mailAddress Email address
5352
     * @param string $linktxt Link text, default will be the email address.
5353
     * @return array 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.
5354
     */
5355
    public function getMailTo($mailAddress, $linktxt)
5356
    {
5357
        $mailAddress = (string)$mailAddress;
5358
        if ((string)$linktxt === '') {
5359
            $linktxt = htmlspecialchars($mailAddress);
5360
        }
5361
5362
        $originalMailToUrl = 'mailto:' . $mailAddress;
5363
        $mailToUrl = $this->processUrl(UrlProcessorInterface::CONTEXT_MAIL, $originalMailToUrl);
5364
5365
        // no processing happened, therefore, the default processing kicks in
5366
        if ($mailToUrl === $originalMailToUrl) {
5367
            $tsfe = $this->getTypoScriptFrontendController();
5368
            if ($tsfe->spamProtectEmailAddresses) {
5369
                $mailToUrl = $this->encryptEmail($mailToUrl, $tsfe->spamProtectEmailAddresses);
5370
                if ($tsfe->spamProtectEmailAddresses !== 'ascii') {
5371
                    $encodedForJsAndHref = rawurlencode(GeneralUtility::quoteJSvalue($mailToUrl));
5372
                    $mailToUrl = 'javascript:linkTo_UnCryptMailto(' . $encodedForJsAndHref . ');';
5373
                }
5374
                $atLabel = trim($tsfe->config['config']['spamProtectEmailAddresses_atSubst']) ?: '(at)';
5375
                $spamProtectedMailAddress = str_replace('@', $atLabel, htmlspecialchars($mailAddress));
5376
                if ($tsfe->config['config']['spamProtectEmailAddresses_lastDotSubst']) {
5377
                    $lastDotLabel = trim($tsfe->config['config']['spamProtectEmailAddresses_lastDotSubst']);
5378
                    $lastDotLabel = $lastDotLabel ?: '(dot)';
5379
                    $spamProtectedMailAddress = preg_replace('/\\.([^\\.]+)$/', $lastDotLabel . '$1', $spamProtectedMailAddress);
5380
                }
5381
                $linktxt = str_ireplace($mailAddress, $spamProtectedMailAddress, $linktxt);
5382
            }
5383
        }
5384
5385
        return [$mailToUrl, $linktxt];
5386
    }
5387
5388
    /**
5389
     * Encryption of email addresses for <A>-tags See the spam protection setup in TS 'config.'
5390
     *
5391
     * @param string $string Input string to en/decode: "mailto:[email protected]
5392
     * @param mixed  $type - either "ascii" or a number between -10 and 10, taken from config.spamProtectEmailAddresses
5393
     * @return string encoded version of $string
5394
     */
5395
    protected function encryptEmail(string $string, $type): string
5396
    {
5397
        $out = '';
5398
        // obfuscates using the decimal HTML entity references for each character
5399
        if ($type === 'ascii') {
5400
            foreach (preg_split('//u', $string, -1, PREG_SPLIT_NO_EMPTY) as $char) {
5401
                $out .= '&#' . mb_ord($char) . ';';
0 ignored issues
show
Bug introduced by
The call to mb_ord() has too few arguments starting with encoding. ( Ignorable by Annotation )

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

5401
                $out .= '&#' . /** @scrutinizer ignore-call */ mb_ord($char) . ';';

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...
5402
            }
5403
        } else {
5404
            // like str_rot13() but with a variable offset and a wider character range
5405
            $len = strlen($string);
5406
            $offset = (int)$type;
5407
            for ($i = 0; $i < $len; $i++) {
5408
                $charValue = ord($string[$i]);
5409
                // 0-9 . , - + / :
5410
                if ($charValue >= 43 && $charValue <= 58) {
5411
                    $out .= $this->encryptCharcode($charValue, 43, 58, $offset);
5412
                } elseif ($charValue >= 64 && $charValue <= 90) {
5413
                    // A-Z @
5414
                    $out .= $this->encryptCharcode($charValue, 64, 90, $offset);
5415
                } elseif ($charValue >= 97 && $charValue <= 122) {
5416
                    // a-z
5417
                    $out .= $this->encryptCharcode($charValue, 97, 122, $offset);
5418
                } else {
5419
                    $out .= $string[$i];
5420
                }
5421
            }
5422
        }
5423
        return $out;
5424
    }
5425
5426
    /**
5427
     * Encryption (or decryption) of a single character.
5428
     * Within the given range the character is shifted with the supplied offset.
5429
     *
5430
     * @param int $n Ordinal of input character
5431
     * @param int $start Start of range
5432
     * @param int $end End of range
5433
     * @param int $offset Offset
5434
     * @return string encoded/decoded version of character
5435
     */
5436
    protected function encryptCharcode($n, $start, $end, $offset)
5437
    {
5438
        $n = $n + $offset;
5439
        if ($offset > 0 && $n > $end) {
5440
            $n = $start + ($n - $end - 1);
5441
        } elseif ($offset < 0 && $n < $start) {
5442
            $n = $end - ($start - $n - 1);
5443
        }
5444
        return chr($n);
5445
    }
5446
5447
    /**
5448
     * Gets the query arguments and assembles them for URLs.
5449
     * Arguments may be removed or set, depending on configuration.
5450
     *
5451
     * @param array $conf Configuration
5452
     * @param array $overruleQueryArguments Multidimensional key/value pairs that overrule incoming query arguments
5453
     * @param bool $forceOverruleArguments If set, key/value pairs not in the query but the overrule array will be set
5454
     * @return string The URL query part (starting with a &)
5455
     */
5456
    public function getQueryArguments($conf, $overruleQueryArguments = [], $forceOverruleArguments = false)
5457
    {
5458
        $exclude = [];
5459
        $method = (string)($conf['method'] ?? '');
5460
        if ($method === 'POST') {
5461
            trigger_error('Assigning typolink.addQueryString.method = POST is not supported anymore since TYPO3 v10.0.', E_USER_WARNING);
5462
            return '';
5463
        }
5464
        if ($method === 'GET,POST' || $method === 'POST,GET') {
5465
            trigger_error('Assigning typolink.addQueryString.method = GET,POST or POST,GET is not supported anymore since TYPO3 v10.0 - falling back to GET.', E_USER_WARNING);
5466
            $method = 'GET';
5467
        }
5468
        if ($method === 'GET') {
5469
            $currentQueryArray = GeneralUtility::_GET();
5470
        } else {
5471
            $currentQueryArray = [];
5472
            parse_str($this->getEnvironmentVariable('QUERY_STRING'), $currentQueryArray);
5473
        }
5474
        if ($conf['exclude'] ?? false) {
5475
            $excludeString = str_replace(',', '&', $conf['exclude']);
5476
            $excludedQueryParts = [];
5477
            parse_str($excludeString, $excludedQueryParts);
5478
            // never repeat id
5479
            $exclude['id'] = 0;
5480
            $newQueryArray = ArrayUtility::arrayDiffAssocRecursive($currentQueryArray, $excludedQueryParts);
5481
        } else {
5482
            $newQueryArray = $currentQueryArray;
5483
        }
5484
        ArrayUtility::mergeRecursiveWithOverrule($newQueryArray, $overruleQueryArguments, $forceOverruleArguments);
5485
        return HttpUtility::buildQueryString($newQueryArray, '&');
5486
    }
5487
5488
    /***********************************************
5489
     *
5490
     * Miscellaneous functions, stand alone
5491
     *
5492
     ***********************************************/
5493
    /**
5494
     * Wrapping a string.
5495
     * Implements the TypoScript "wrap" property.
5496
     * Example: $content = "HELLO WORLD" and $wrap = "<strong> | </strong>", result: "<strong>HELLO WORLD</strong>"
5497
     *
5498
     * @param string $content The content to wrap
5499
     * @param string $wrap The wrap value, eg. "<strong> | </strong>
5500
     * @param string $char The char used to split the wrapping value, default is "|
5501
     * @return string Wrapped input string
5502
     * @see noTrimWrap()
5503
     */
5504
    public function wrap($content, $wrap, $char = '|')
5505
    {
5506
        if ($wrap) {
5507
            $wrapArr = explode($char, $wrap);
5508
            $content = trim($wrapArr[0] ?? '') . $content . trim($wrapArr[1] ?? '');
5509
        }
5510
        return $content;
5511
    }
5512
5513
    /**
5514
     * Wrapping a string, preserving whitespace in wrap value.
5515
     * Notice that the wrap value uses part 1/2 to wrap (and not 0/1 which wrap() does)
5516
     *
5517
     * @param string $content The content to wrap, eg. "HELLO WORLD
5518
     * @param string $wrap The wrap value, eg. " | <strong> | </strong>
5519
     * @param string $char The char used to split the wrapping value, default is "|"
5520
     * @return string Wrapped input string, eg. " <strong> HELLO WORD </strong>
5521
     * @see wrap()
5522
     */
5523
    public function noTrimWrap($content, $wrap, $char = '|')
5524
    {
5525
        if ($wrap) {
5526
            // expects to be wrapped with (at least) 3 characters (before, middle, after)
5527
            // anything else is not taken into account
5528
            $wrapArr = explode($char, $wrap, 4);
5529
            $content = $wrapArr[1] . $content . $wrapArr[2];
5530
        }
5531
        return $content;
5532
    }
5533
5534
    /**
5535
     * Calling a user function/class-method
5536
     * Notice: For classes the instantiated object will have the internal variable, $cObj, set to be a *reference* to $this (the parent/calling object).
5537
     *
5538
     * @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.
5539
     * @param array $conf The TypoScript configuration to pass the function
5540
     * @param string $content The content string to pass the function
5541
     * @return string The return content from the function call. Should probably be a string.
5542
     * @see stdWrap()
5543
     * @see typoLink()
5544
     * @see _parseFunc()
5545
     */
5546
    public function callUserFunction($funcName, $conf, $content)
5547
    {
5548
        // Split parts
5549
        $parts = explode('->', $funcName);
5550
        if (count($parts) === 2) {
5551
            // Check whether PHP class is available
5552
            if (class_exists($parts[0])) {
5553
                if ($this->container && $this->container->has($parts[0])) {
5554
                    $classObj = $this->container->get($parts[0]);
5555
                } else {
5556
                    $classObj = GeneralUtility::makeInstance($parts[0]);
5557
                }
5558
                if (is_object($classObj) && method_exists($classObj, $parts[1])) {
5559
                    $classObj->cObj = $this;
5560
                    $content = call_user_func_array([
5561
                        $classObj,
5562
                        $parts[1]
5563
                    ], [
5564
                        $content,
5565
                        $conf
5566
                    ]);
5567
                } else {
5568
                    $this->getTimeTracker()->setTSlogMessage('Method "' . $parts[1] . '" did not exist in class "' . $parts[0] . '"', 3);
5569
                }
5570
            } else {
5571
                $this->getTimeTracker()->setTSlogMessage('Class "' . $parts[0] . '" did not exist', 3);
5572
            }
5573
        } elseif (function_exists($funcName)) {
5574
            $content = call_user_func($funcName, $content, $conf);
5575
        } else {
5576
            $this->getTimeTracker()->setTSlogMessage('Function "' . $funcName . '" did not exist', 3);
5577
        }
5578
        return $content;
5579
    }
5580
5581
    /**
5582
     * Cleans up a string of keywords. Keywords at splitted by "," (comma)  ";" (semi colon) and linebreak
5583
     *
5584
     * @param string $content String of keywords
5585
     * @return string Cleaned up string, keywords will be separated by a comma only.
5586
     */
5587
    public function keywords($content)
5588
    {
5589
        $listArr = preg_split('/[,;' . LF . ']/', $content);
5590
        foreach ($listArr as $k => $v) {
5591
            $listArr[$k] = trim($v);
5592
        }
5593
        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

5593
        return implode(',', /** @scrutinizer ignore-type */ $listArr);
Loading history...
5594
    }
5595
5596
    /**
5597
     * Changing character case of a string, converting typically used western charset characters as well.
5598
     *
5599
     * @param string $theValue The string to change case for.
5600
     * @param string $case The direction; either "upper" or "lower
5601
     * @return string
5602
     * @see HTMLcaseshift()
5603
     */
5604
    public function caseshift($theValue, $case)
5605
    {
5606
        switch (strtolower($case)) {
5607
            case 'upper':
5608
                $theValue = mb_strtoupper($theValue, 'utf-8');
5609
                break;
5610
            case 'lower':
5611
                $theValue = mb_strtolower($theValue, 'utf-8');
5612
                break;
5613
            case 'capitalize':
5614
                $theValue = mb_convert_case($theValue, MB_CASE_TITLE, 'utf-8');
5615
                break;
5616
            case 'ucfirst':
5617
                $firstChar = mb_substr($theValue, 0, 1, 'utf-8');
5618
                $firstChar = mb_strtoupper($firstChar, 'utf-8');
5619
                $remainder = mb_substr($theValue, 1, null, 'utf-8');
5620
                $theValue = $firstChar . $remainder;
5621
                break;
5622
            case 'lcfirst':
5623
                $firstChar = mb_substr($theValue, 0, 1, 'utf-8');
5624
                $firstChar = mb_strtolower($firstChar, 'utf-8');
5625
                $remainder = mb_substr($theValue, 1, null, 'utf-8');
5626
                $theValue = $firstChar . $remainder;
5627
                break;
5628
            case 'uppercamelcase':
5629
                $theValue = GeneralUtility::underscoredToUpperCamelCase($theValue);
5630
                break;
5631
            case 'lowercamelcase':
5632
                $theValue = GeneralUtility::underscoredToLowerCamelCase($theValue);
5633
                break;
5634
        }
5635
        return $theValue;
5636
    }
5637
5638
    /**
5639
     * Shifts the case of characters outside of HTML tags in the input string
5640
     *
5641
     * @param string $theValue The string to change case for.
5642
     * @param string $case The direction; either "upper" or "lower
5643
     * @return string
5644
     * @see caseshift()
5645
     */
5646
    public function HTMLcaseshift($theValue, $case)
5647
    {
5648
        $inside = 0;
5649
        $newVal = '';
5650
        $pointer = 0;
5651
        $totalLen = strlen($theValue);
5652
        do {
5653
            if (!$inside) {
5654
                $len = strcspn(substr($theValue, $pointer), '<');
5655
                $newVal .= $this->caseshift(substr($theValue, $pointer, $len), $case);
5656
                $inside = 1;
5657
            } else {
5658
                $len = strcspn(substr($theValue, $pointer), '>') + 1;
5659
                $newVal .= substr($theValue, $pointer, $len);
5660
                $inside = 0;
5661
            }
5662
            $pointer += $len;
5663
        } while ($pointer < $totalLen);
5664
        return $newVal;
5665
    }
5666
5667
    /**
5668
     * Returns the 'age' of the tstamp $seconds
5669
     *
5670
     * @param int $seconds Seconds to return age for. Example: "70" => "1 min", "3601" => "1 hrs
5671
     * @param string $labels The labels of the individual units. Defaults to : ' min| hrs| days| yrs'
5672
     * @return string The formatted string
5673
     */
5674
    public function calcAge($seconds, $labels)
5675
    {
5676
        if (MathUtility::canBeInterpretedAsInteger($labels)) {
5677
            $labels = ' min| hrs| days| yrs| min| hour| day| year';
5678
        } else {
5679
            $labels = str_replace('"', '', $labels);
5680
        }
5681
        $labelArr = explode('|', $labels);
5682
        if (count($labelArr) === 4) {
5683
            $labelArr = array_merge($labelArr, $labelArr);
5684
        }
5685
        $absSeconds = abs($seconds);
5686
        $sign = $seconds > 0 ? 1 : -1;
5687
        if ($absSeconds < 3600) {
5688
            $val = round($absSeconds / 60);
5689
            $seconds = $sign * $val . ($val == 1 ? $labelArr[4] : $labelArr[0]);
5690
        } elseif ($absSeconds < 24 * 3600) {
5691
            $val = round($absSeconds / 3600);
5692
            $seconds = $sign * $val . ($val == 1 ? $labelArr[5] : $labelArr[1]);
5693
        } elseif ($absSeconds < 365 * 24 * 3600) {
5694
            $val = round($absSeconds / (24 * 3600));
5695
            $seconds = $sign * $val . ($val == 1 ? $labelArr[6] : $labelArr[2]);
5696
        } else {
5697
            $val = round($absSeconds / (365 * 24 * 3600));
5698
            $seconds = $sign * $val . ($val == 1 ? ($labelArr[7] ?? null) : ($labelArr[3] ?? null));
5699
        }
5700
        return $seconds;
5701
    }
5702
5703
    /**
5704
     * Sends a notification email
5705
     *
5706
     * @param string $message The message content. If blank, no email is sent.
5707
     * @param string $recipients Comma list of recipient email addresses
5708
     * @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.
5709
     * @param string $senderAddress "From" email address
5710
     * @param string $senderName Optional "From" name
5711
     * @param string $replyTo Optional "Reply-To" header email address.
5712
     * @return bool Returns TRUE if sent
5713
     * @deprecated ContentObjectRenderer::sendNotifyEmail is deprecated and will be removed in TYPO3 v11. Consider using the mail API directly
5714
     */
5715
    public function sendNotifyEmail($message, $recipients, $cc, $senderAddress, $senderName = '', $replyTo = '')
5716
    {
5717
        trigger_error('ContentObjectRenderer::sendNotifyEmail is deprecated and will be removed in TYPO3 v11. Consider using the mail API directly.', E_USER_DEPRECATED);
5718
        /** @var MailMessage $mail */
5719
        $mail = GeneralUtility::makeInstance(MailMessage::class);
5720
        $senderName = trim($senderName);
5721
        $senderAddress = trim($senderAddress);
5722
        if ($senderAddress !== '') {
5723
            $mail->from(new Address($senderAddress, $senderName));
5724
        }
5725
        $parsedReplyTo = MailUtility::parseAddresses($replyTo);
5726
        if (!empty($parsedReplyTo)) {
5727
            $mail->replyTo($parsedReplyTo);
0 ignored issues
show
Bug introduced by
$parsedReplyTo of type array is incompatible with the type Symfony\Component\Mime\Address|string expected by parameter $addresses of Symfony\Component\Mime\Email::replyTo(). ( Ignorable by Annotation )

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

5727
            $mail->replyTo(/** @scrutinizer ignore-type */ $parsedReplyTo);
Loading history...
5728
        }
5729
        $message = trim($message);
5730
        if ($message !== '') {
5731
            // First line is subject
5732
            $messageParts = explode(LF, $message, 2);
5733
            $subject = trim($messageParts[0]);
5734
            $plainMessage = trim($messageParts[1]);
5735
            $parsedRecipients = MailUtility::parseAddresses($recipients);
5736
            if (!empty($parsedRecipients)) {
5737
                $mail->to(...$parsedRecipients)
5738
                    ->subject($subject)
5739
                    ->text($plainMessage);
5740
                $mail->send();
5741
            }
5742
            $parsedCc = MailUtility::parseAddresses($cc);
5743
            if (!empty($parsedCc)) {
5744
                $from = $mail->getFrom();
5745
                /** @var MailMessage $mail */
5746
                $mail = GeneralUtility::makeInstance(MailMessage::class);
5747
                if (!empty($parsedReplyTo)) {
5748
                    $mail->replyTo($parsedReplyTo);
5749
                }
5750
                $mail->from($from)
0 ignored issues
show
Bug introduced by
$from of type array is incompatible with the type Symfony\Component\Mime\Address|string expected by parameter $addresses of Symfony\Component\Mime\Email::from(). ( Ignorable by Annotation )

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

5750
                $mail->from(/** @scrutinizer ignore-type */ $from)
Loading history...
5751
                    ->to(...$parsedCc)
5752
                    ->subject($subject)
5753
                    ->text($plainMessage);
5754
                $mail->send();
5755
            }
5756
            return true;
5757
        }
5758
        return false;
5759
    }
5760
5761
    /**
5762
     * Resolves a TypoScript reference value to the full set of properties BUT overridden with any local properties set.
5763
     * So the reference is resolved but overlaid with local TypoScript properties of the reference value.
5764
     *
5765
     * @param array $confArr The TypoScript array
5766
     * @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.
5767
     * @return array The modified TypoScript array
5768
     */
5769
    public function mergeTSRef($confArr, $prop)
5770
    {
5771
        if ($confArr[$prop][0] === '<') {
5772
            $key = trim(substr($confArr[$prop], 1));
5773
            $cF = GeneralUtility::makeInstance(TypoScriptParser::class);
5774
            // $name and $conf is loaded with the referenced values.
5775
            $old_conf = $confArr[$prop . '.'];
5776
            [, $conf] = $cF->getVal($key, $this->getTypoScriptFrontendController()->tmpl->setup);
5777
            if (is_array($old_conf) && !empty($old_conf)) {
5778
                $conf = is_array($conf) ? array_replace_recursive($conf, $old_conf) : $old_conf;
5779
            }
5780
            $confArr[$prop . '.'] = $conf;
5781
        }
5782
        return $confArr;
5783
    }
5784
5785
    /***********************************************
5786
     *
5787
     * Database functions, making of queries
5788
     *
5789
     ***********************************************/
5790
5791
    /**
5792
     * Generates a list of Page-uid's from $id. List does not include $id itself
5793
     * (unless the id specified is negative in which case it does!)
5794
     * The only pages WHICH PREVENTS DECENDING in a branch are
5795
     * - deleted pages,
5796
     * - pages in a recycler (doktype = 255) or of the Backend User Section (doktpe = 6) type
5797
     * - pages that has the extendToSubpages set, WHERE start/endtime, hidden
5798
     * and fe_users would hide the records.
5799
     * Apart from that, pages with enable-fields excluding them, will also be
5800
     * removed. HOWEVER $dontCheckEnableFields set will allow
5801
     * enableFields-excluded pages to be included anyway - including
5802
     * extendToSubpages sections!
5803
     * Mount Pages are also descended but notice that these ID numbers are not
5804
     * useful for links unless the correct MPvar is set.
5805
     *
5806
     * @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!
5807
     * @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 descend...)
5808
     * @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'
5809
     * @param bool $dontCheckEnableFields See function description
5810
     * @param string $addSelectFields Additional fields to select. Syntax: ",[fieldname],[fieldname],...
5811
     * @param string $moreWhereClauses Additional where clauses. Syntax: " AND [fieldname]=[value] AND ...
5812
     * @param array $prevId_array array of IDs from previous recursions. In order to prevent infinite loops with mount pages.
5813
     * @param int $recursionLevel Internal: Zero for the first recursion, incremented for each recursive call.
5814
     * @return string Returns the list of ids as a comma separated string
5815
     * @see TypoScriptFrontendController::checkEnableFields()
5816
     * @see TypoScriptFrontendController::checkPagerecordForIncludeSection()
5817
     */
5818
    public function getTreeList($id, $depth, $begin = 0, $dontCheckEnableFields = false, $addSelectFields = '', $moreWhereClauses = '', array $prevId_array = [], $recursionLevel = 0)
5819
    {
5820
        $id = (int)$id;
5821
        if (!$id) {
5822
            return '';
5823
        }
5824
5825
        // Init vars:
5826
        $allFields = 'uid,hidden,starttime,endtime,fe_group,extendToSubpages,doktype,php_tree_stop,mount_pid,mount_pid_ol,t3ver_state' . $addSelectFields;
5827
        $depth = (int)$depth;
5828
        $begin = (int)$begin;
5829
        $theList = [];
5830
        $addId = 0;
5831
        $requestHash = '';
5832
5833
        // First level, check id (second level, this is done BEFORE the recursive call)
5834
        $tsfe = $this->getTypoScriptFrontendController();
5835
        if (!$recursionLevel) {
5836
            // Check tree list cache
5837
            // First, create the hash for this request - not sure yet whether we need all these parameters though
5838
            $parameters = [
5839
                $id,
5840
                $depth,
5841
                $begin,
5842
                $dontCheckEnableFields,
5843
                $addSelectFields,
5844
                $moreWhereClauses,
5845
                $prevId_array,
5846
                GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('frontend.user', 'groupIds', [0, -1])
5847
            ];
5848
            $requestHash = md5(serialize($parameters));
5849
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
5850
                ->getQueryBuilderForTable('cache_treelist');
5851
            $cacheEntry = $queryBuilder->select('treelist')
5852
                ->from('cache_treelist')
5853
                ->where(
5854
                    $queryBuilder->expr()->eq(
5855
                        'md5hash',
5856
                        $queryBuilder->createNamedParameter($requestHash, \PDO::PARAM_STR)
5857
                    ),
5858
                    $queryBuilder->expr()->orX(
5859
                        $queryBuilder->expr()->gt(
5860
                            'expires',
5861
                            $queryBuilder->createNamedParameter($GLOBALS['EXEC_TIME'], \PDO::PARAM_INT)
5862
                        ),
5863
                        $queryBuilder->expr()->eq('expires', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
5864
                    )
5865
                )
5866
                ->setMaxResults(1)
5867
                ->execute()
5868
                ->fetch();
5869
5870
            if (is_array($cacheEntry)) {
5871
                // Cache hit
5872
                return $cacheEntry['treelist'];
5873
            }
5874
            // If Id less than zero it means we should add the real id to list:
5875
            if ($id < 0) {
5876
                $addId = $id = abs($id);
5877
            }
5878
            // Check start page:
5879
            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\Core\Domain\Re...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

5879
            if ($tsfe->sys_page->getRawRecord('pages', /** @scrutinizer ignore-type */ $id, 'uid')) {
Loading history...
5880
                // Find mount point if any:
5881
                $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\Core\Domain\Re...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

5881
                $mount_info = $tsfe->sys_page->getMountPointInfo(/** @scrutinizer ignore-type */ $id);
Loading history...
5882
                if (is_array($mount_info)) {
5883
                    $id = $mount_info['mount_pid'];
5884
                    // In Overlay mode, use the mounted page uid as added ID!:
5885
                    if ($addId && $mount_info['overlay']) {
5886
                        $addId = $id;
5887
                    }
5888
                }
5889
            } else {
5890
                // Return blank if the start page was NOT found at all!
5891
                return '';
5892
            }
5893
        }
5894
        // Add this ID to the array of IDs
5895
        if ($begin <= 0) {
5896
            $prevId_array[] = $id;
5897
        }
5898
        // Select sublevel:
5899
        if ($depth > 0) {
5900
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
5901
            $queryBuilder->getRestrictions()
5902
                ->removeAll()
5903
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
5904
            $queryBuilder->select(...GeneralUtility::trimExplode(',', $allFields, true))
5905
                ->from('pages')
5906
                ->where(
5907
                    $queryBuilder->expr()->eq(
5908
                        'pid',
5909
                        $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
5910
                    ),
5911
                    // tree is only built by language=0 pages
5912
                    $queryBuilder->expr()->eq('sys_language_uid', 0)
5913
                )
5914
                ->orderBy('sorting');
5915
5916
            if (!empty($moreWhereClauses)) {
5917
                $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($moreWhereClauses));
5918
            }
5919
5920
            $result = $queryBuilder->execute();
5921
            while ($row = $result->fetch()) {
5922
                /** @var VersionState $versionState */
5923
                $versionState = VersionState::cast($row['t3ver_state']);
5924
                $tsfe->sys_page->versionOL('pages', $row);
5925
                if ((int)$row['doktype'] === PageRepository::DOKTYPE_RECYCLER
5926
                    || (int)$row['doktype'] === PageRepository::DOKTYPE_BE_USER_SECTION
5927
                    || $versionState->indicatesPlaceholder()
5928
                ) {
5929
                    // Doing this after the overlay to make sure changes
5930
                    // in the overlay are respected.
5931
                    // However, we do not process pages below of and
5932
                    // including of type recycler and BE user section
5933
                    continue;
5934
                }
5935
                // Find mount point if any:
5936
                $next_id = $row['uid'];
5937
                $mount_info = $tsfe->sys_page->getMountPointInfo($next_id, $row);
5938
                // Overlay mode:
5939
                if (is_array($mount_info) && $mount_info['overlay']) {
5940
                    $next_id = $mount_info['mount_pid'];
5941
                    $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
5942
                        ->getQueryBuilderForTable('pages');
5943
                    $queryBuilder->getRestrictions()
5944
                        ->removeAll()
5945
                        ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
5946
                    $queryBuilder->select(...GeneralUtility::trimExplode(',', $allFields, true))
5947
                        ->from('pages')
5948
                        ->where(
5949
                            $queryBuilder->expr()->eq(
5950
                                'uid',
5951
                                $queryBuilder->createNamedParameter($next_id, \PDO::PARAM_INT)
5952
                            )
5953
                        )
5954
                        ->orderBy('sorting')
5955
                        ->setMaxResults(1);
5956
5957
                    if (!empty($moreWhereClauses)) {
5958
                        $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($moreWhereClauses));
5959
                    }
5960
5961
                    $row = $queryBuilder->execute()->fetch();
5962
                    $tsfe->sys_page->versionOL('pages', $row);
5963
                    if ((int)$row['doktype'] === PageRepository::DOKTYPE_RECYCLER
5964
                        || (int)$row['doktype'] === PageRepository::DOKTYPE_BE_USER_SECTION
5965
                        || $versionState->indicatesPlaceholder()
5966
                    ) {
5967
                        // Doing this after the overlay to make sure
5968
                        // changes in the overlay are respected.
5969
                        // see above
5970
                        continue;
5971
                    }
5972
                }
5973
                // Add record:
5974
                if ($dontCheckEnableFields || $tsfe->checkPagerecordForIncludeSection($row)) {
5975
                    // Add ID to list:
5976
                    if ($begin <= 0) {
5977
                        if ($dontCheckEnableFields || $tsfe->checkEnableFields($row)) {
5978
                            $theList[] = $next_id;
5979
                        }
5980
                    }
5981
                    // Next level:
5982
                    if ($depth > 1 && !$row['php_tree_stop']) {
5983
                        // Normal mode:
5984
                        if (is_array($mount_info) && !$mount_info['overlay']) {
5985
                            $next_id = $mount_info['mount_pid'];
5986
                        }
5987
                        // Call recursively, if the id is not in prevID_array:
5988
                        if (!in_array($next_id, $prevId_array)) {
5989
                            $theList = array_merge(
5990
                                GeneralUtility::intExplode(
5991
                                    ',',
5992
                                    $this->getTreeList(
5993
                                        $next_id,
5994
                                        $depth - 1,
5995
                                        $begin - 1,
5996
                                        $dontCheckEnableFields,
5997
                                        $addSelectFields,
5998
                                        $moreWhereClauses,
5999
                                        $prevId_array,
6000
                                        $recursionLevel + 1
6001
                                    ),
6002
                                    true
6003
                                ),
6004
                                $theList
6005
                            );
6006
                        }
6007
                    }
6008
                }
6009
            }
6010
        }
6011
        // If first run, check if the ID should be returned:
6012
        if (!$recursionLevel) {
6013
            if ($addId) {
6014
                if ($begin > 0) {
6015
                    $theList[] = 0;
6016
                } else {
6017
                    $theList[] = $addId;
6018
                }
6019
            }
6020
6021
            $cacheEntry = [
6022
                'md5hash' => $requestHash,
6023
                'pid' => $id,
6024
                'treelist' => implode(',', $theList),
6025
                'tstamp' => $GLOBALS['EXEC_TIME'],
6026
            ];
6027
6028
            // Only add to cache if not logged into TYPO3 Backend
6029
            if (!$this->getFrontendBackendUser() instanceof AbstractUserAuthentication) {
0 ignored issues
show
introduced by
$this->getFrontendBackendUser() is always a sub-type of TYPO3\CMS\Core\Authentic...tractUserAuthentication.
Loading history...
6030
                $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('cache_treelist');
6031
                try {
6032
                    $connection->transactional(function ($connection) use ($cacheEntry) {
6033
                        $connection->insert('cache_treelist', $cacheEntry);
6034
                    });
6035
                } catch (\Throwable $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
6036
                }
6037
            }
6038
        }
6039
6040
        return implode(',', $theList);
6041
    }
6042
6043
    /**
6044
     * Generates a search where clause based on the input search words (AND operation - all search words must be found in record.)
6045
     * 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%")'
6046
     *
6047
     * @param string $searchWords The search words. These will be separated by space and comma.
6048
     * @param string $searchFieldList The fields to search in
6049
     * @param string $searchTable The table name you search in (recommended for DBAL compliance. Will be prepended field names as well)
6050
     * @return string The WHERE clause.
6051
     */
6052
    public function searchWhere($searchWords, $searchFieldList, $searchTable)
6053
    {
6054
        if (!$searchWords) {
6055
            return '';
6056
        }
6057
6058
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
6059
            ->getQueryBuilderForTable($searchTable);
6060
6061
        $prefixTableName = $searchTable ? $searchTable . '.' : '';
6062
6063
        $where = $queryBuilder->expr()->andX();
6064
        $searchFields = explode(',', $searchFieldList);
6065
        $searchWords = preg_split('/[ ,]/', $searchWords);
6066
        foreach ($searchWords as $searchWord) {
6067
            $searchWord = trim($searchWord);
6068
            if (strlen($searchWord) < 3) {
6069
                continue;
6070
            }
6071
            $searchWordConstraint = $queryBuilder->expr()->orX();
6072
            $searchWord = $queryBuilder->escapeLikeWildcards($searchWord);
6073
            foreach ($searchFields as $field) {
6074
                $searchWordConstraint->add(
6075
                    $queryBuilder->expr()->like($prefixTableName . $field, $queryBuilder->quote('%' . $searchWord . '%'))
6076
                );
6077
            }
6078
6079
            if ($searchWordConstraint->count()) {
6080
                $where->add($searchWordConstraint);
6081
            }
6082
        }
6083
6084
        if ((string)$where === '') {
6085
            return '';
6086
        }
6087
6088
        return ' AND (' . (string)$where . ')';
6089
    }
6090
6091
    /**
6092
     * Executes a SELECT query for records from $table and with conditions based on the configuration in the $conf array
6093
     * This function is preferred over ->getQuery() if you just need to create and then execute a query.
6094
     *
6095
     * @param string $table The table name
6096
     * @param array $conf The TypoScript configuration properties
6097
     * @return Statement
6098
     * @see getQuery()
6099
     */
6100
    public function exec_getQuery($table, $conf)
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::exec_getQuery" is not in camel caps format
Loading history...
6101
    {
6102
        $statement = $this->getQuery($table, $conf);
6103
        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
6104
6105
        return $connection->executeQuery($statement);
6106
    }
6107
6108
    /**
6109
     * Executes a SELECT query for records from $table and with conditions based on the configuration in the $conf array
6110
     * and overlays with translation and version if available
6111
     *
6112
     * @param string $tableName the name of the TCA database table
6113
     * @param array $queryConfiguration The TypoScript configuration properties, see .select in TypoScript reference
6114
     * @return array The records
6115
     * @throws \UnexpectedValueException
6116
     */
6117
    public function getRecords($tableName, array $queryConfiguration)
6118
    {
6119
        $records = [];
6120
6121
        $statement = $this->exec_getQuery($tableName, $queryConfiguration);
6122
6123
        $tsfe = $this->getTypoScriptFrontendController();
6124
        while ($row = $statement->fetch()) {
6125
            // Versioning preview:
6126
            $tsfe->sys_page->versionOL($tableName, $row, true);
6127
6128
            // Language overlay:
6129
            if (is_array($row)) {
6130
                $row = $tsfe->sys_page->getLanguageOverlay($tableName, $row);
6131
            }
6132
6133
            // Might be unset in the language overlay
6134
            if (is_array($row)) {
6135
                $records[] = $row;
6136
            }
6137
        }
6138
6139
        return $records;
6140
    }
6141
6142
    /**
6143
     * Creates and returns a SELECT query for records from $table and with conditions based on the configuration in the $conf array
6144
     * Implements the "select" function in TypoScript
6145
     *
6146
     * @param string $table See ->exec_getQuery()
6147
     * @param array $conf See ->exec_getQuery()
6148
     * @param bool $returnQueryArray If set, the function will return the query not as a string but array with the various parts. RECOMMENDED!
6149
     * @return mixed A SELECT query if $returnQueryArray is FALSE, otherwise the SELECT query in an array as parts.
6150
     * @throws \RuntimeException
6151
     * @throws \InvalidArgumentException
6152
     * @internal
6153
     * @see numRows()
6154
     */
6155
    public function getQuery($table, $conf, $returnQueryArray = false)
6156
    {
6157
        // Resolve stdWrap in these properties first
6158
        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
6159
        $properties = [
6160
            'pidInList',
6161
            'uidInList',
6162
            'languageField',
6163
            'selectFields',
6164
            'max',
6165
            'begin',
6166
            'groupBy',
6167
            'orderBy',
6168
            'join',
6169
            'leftjoin',
6170
            'rightjoin',
6171
            'recursive',
6172
            'where'
6173
        ];
6174
        foreach ($properties as $property) {
6175
            $conf[$property] = trim(
6176
                isset($conf[$property . '.'])
6177
                    ? $this->stdWrap($conf[$property], $conf[$property . '.'])
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
6178
                    : $conf[$property]
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
6179
            );
6180
            if ($conf[$property] === '') {
6181
                unset($conf[$property]);
6182
            } elseif (in_array($property, ['languageField', 'selectFields', 'join', 'leftJoin', 'rightJoin', 'where'], true)) {
6183
                $conf[$property] = QueryHelper::quoteDatabaseIdentifiers($connection, $conf[$property]);
6184
            }
6185
            if (isset($conf[$property . '.'])) {
6186
                // stdWrapping already done, so remove the sub-array
6187
                unset($conf[$property . '.']);
6188
            }
6189
        }
6190
        // Handle PDO-style named parameter markers first
6191
        $queryMarkers = $this->getQueryMarkers($table, $conf);
6192
        // Replace the markers in the non-stdWrap properties
6193
        foreach ($queryMarkers as $marker => $markerValue) {
6194
            $properties = [
6195
                'uidInList',
6196
                'selectFields',
6197
                'where',
6198
                'max',
6199
                'begin',
6200
                'groupBy',
6201
                'orderBy',
6202
                'join',
6203
                'leftjoin',
6204
                'rightjoin'
6205
            ];
6206
            foreach ($properties as $property) {
6207
                if ($conf[$property]) {
6208
                    $conf[$property] = str_replace('###' . $marker . '###', $markerValue, $conf[$property]);
6209
                }
6210
            }
6211
        }
6212
6213
        // Construct WHERE clause:
6214
        // Handle recursive function for the pidInList
6215
        if (isset($conf['recursive'])) {
6216
            $conf['recursive'] = (int)$conf['recursive'];
6217
            if ($conf['recursive'] > 0) {
6218
                $pidList = GeneralUtility::trimExplode(',', $conf['pidInList'], true);
6219
                array_walk($pidList, function (&$storagePid) {
6220
                    if ($storagePid === 'this') {
6221
                        $storagePid = $this->getTypoScriptFrontendController()->id;
6222
                    }
6223
                    if ($storagePid > 0) {
6224
                        $storagePid = -$storagePid;
6225
                    }
6226
                });
6227
                $expandedPidList = [];
6228
                foreach ($pidList as $value) {
6229
                    // Implementation of getTreeList allows to pass the id negative to include
6230
                    // it into the result otherwise only childpages are returned
6231
                    $expandedPidList = array_merge(
6232
                        GeneralUtility::intExplode(',', $this->getTreeList($value, $conf['recursive'])),
6233
                        $expandedPidList
6234
                    );
6235
                }
6236
                $conf['pidInList'] = implode(',', $expandedPidList);
6237
            }
6238
        }
6239
        if ((string)$conf['pidInList'] === '') {
6240
            $conf['pidInList'] = 'this';
6241
        }
6242
6243
        $queryParts = $this->getQueryConstraints($table, $conf);
6244
6245
        $queryBuilder = $connection->createQueryBuilder();
6246
        // @todo Check against getQueryConstraints, can probably use FrontendRestrictions
6247
        // @todo here and remove enableFields there.
6248
        $queryBuilder->getRestrictions()->removeAll();
6249
        $queryBuilder->select('*')->from($table);
6250
6251
        if ($queryParts['where']) {
6252
            $queryBuilder->where($queryParts['where']);
6253
        }
6254
6255
        if ($queryParts['groupBy']) {
6256
            $queryBuilder->groupBy(...$queryParts['groupBy']);
6257
        }
6258
6259
        if (is_array($queryParts['orderBy'])) {
6260
            foreach ($queryParts['orderBy'] as $orderBy) {
6261
                $queryBuilder->addOrderBy(...$orderBy);
6262
            }
6263
        }
6264
6265
        // Fields:
6266
        if ($conf['selectFields']) {
6267
            $queryBuilder->selectLiteral($this->sanitizeSelectPart($conf['selectFields'], $table));
6268
        }
6269
6270
        // Setting LIMIT:
6271
        $error = false;
6272
        if ($conf['max'] || $conf['begin']) {
6273
            // Finding the total number of records, if used:
6274
            if (strpos(strtolower($conf['begin'] . $conf['max']), 'total') !== false) {
6275
                $countQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
6276
                $countQueryBuilder->getRestrictions()->removeAll();
6277
                $countQueryBuilder->count('*')
6278
                    ->from($table)
6279
                    ->where($queryParts['where']);
6280
6281
                if ($queryParts['groupBy']) {
6282
                    $countQueryBuilder->groupBy(...$queryParts['groupBy']);
6283
                }
6284
6285
                try {
6286
                    $count = $countQueryBuilder->execute()->fetchColumn(0);
6287
                    $conf['max'] = str_ireplace('total', $count, $conf['max']);
6288
                    $conf['begin'] = str_ireplace('total', $count, $conf['begin']);
6289
                } catch (DBALException $e) {
6290
                    $this->getTimeTracker()->setTSlogMessage($e->getPrevious()->getMessage());
6291
                    $error = true;
6292
                }
6293
            }
6294
6295
            if (!$error) {
6296
                $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

6296
                $conf['begin'] = MathUtility::forceIntegerInRange(/** @scrutinizer ignore-type */ ceil($this->calc($conf['begin'])), 0);
Loading history...
6297
                $conf['max'] = MathUtility::forceIntegerInRange(ceil($this->calc($conf['max'])), 0);
6298
                if ($conf['begin'] > 0) {
6299
                    $queryBuilder->setFirstResult($conf['begin']);
6300
                }
6301
                $queryBuilder->setMaxResults($conf['max'] ?: 100000);
6302
            }
6303
        }
6304
6305
        if (!$error) {
6306
            // Setting up tablejoins:
6307
            if ($conf['join']) {
6308
                $joinParts = QueryHelper::parseJoin($conf['join']);
6309
                $queryBuilder->join(
6310
                    $table,
6311
                    $joinParts['tableName'],
6312
                    $joinParts['tableAlias'],
6313
                    $joinParts['joinCondition']
6314
                );
6315
            } elseif ($conf['leftjoin']) {
6316
                $joinParts = QueryHelper::parseJoin($conf['leftjoin']);
6317
                $queryBuilder->leftJoin(
6318
                    $table,
6319
                    $joinParts['tableName'],
6320
                    $joinParts['tableAlias'],
6321
                    $joinParts['joinCondition']
6322
                );
6323
            } elseif ($conf['rightjoin']) {
6324
                $joinParts = QueryHelper::parseJoin($conf['rightjoin']);
6325
                $queryBuilder->rightJoin(
6326
                    $table,
6327
                    $joinParts['tableName'],
6328
                    $joinParts['tableAlias'],
6329
                    $joinParts['joinCondition']
6330
                );
6331
            }
6332
6333
            // Convert the QueryBuilder object into a SQL statement.
6334
            $query = $queryBuilder->getSQL();
6335
6336
            // Replace the markers in the queryParts to handle stdWrap enabled properties
6337
            foreach ($queryMarkers as $marker => $markerValue) {
6338
                // @todo Ugly hack that needs to be cleaned up, with the current architecture
6339
                // @todo for exec_Query / getQuery it's the best we can do.
6340
                $query = str_replace('###' . $marker . '###', $markerValue, $query);
6341
                foreach ($queryParts as $queryPartKey => &$queryPartValue) {
6342
                    $queryPartValue = str_replace('###' . $marker . '###', $markerValue, $queryPartValue);
6343
                }
6344
                unset($queryPartValue);
6345
            }
6346
6347
            return $returnQueryArray ? $this->getQueryArray($queryBuilder) : $query;
6348
        }
6349
6350
        return '';
6351
    }
6352
6353
    /**
6354
     * Helper to transform a QueryBuilder object into a queryParts array that can be used
6355
     * with exec_SELECT_queryArray
6356
     *
6357
     * @param \TYPO3\CMS\Core\Database\Query\QueryBuilder $queryBuilder
6358
     * @return array
6359
     * @throws \RuntimeException
6360
     */
6361
    protected function getQueryArray(QueryBuilder $queryBuilder)
6362
    {
6363
        $fromClauses = [];
6364
        $knownAliases = [];
6365
        $queryParts = [];
6366
6367
        // Loop through all FROM clauses
6368
        foreach ($queryBuilder->getQueryPart('from') as $from) {
6369
            if ($from['alias'] === null) {
6370
                $tableSql = $from['table'];
6371
                $tableReference = $from['table'];
6372
            } else {
6373
                $tableSql = $from['table'] . ' ' . $from['alias'];
6374
                $tableReference = $from['alias'];
6375
            }
6376
6377
            $knownAliases[$tableReference] = true;
6378
6379
            $fromClauses[$tableReference] = $tableSql . $this->getQueryArrayJoinHelper(
6380
                $tableReference,
6381
                $queryBuilder->getQueryPart('join'),
6382
                $knownAliases
6383
            );
6384
        }
6385
6386
        $queryParts['SELECT'] = implode(', ', $queryBuilder->getQueryPart('select'));
6387
        $queryParts['FROM'] = implode(', ', $fromClauses);
6388
        $queryParts['WHERE'] = (string)$queryBuilder->getQueryPart('where') ?: '';
6389
        $queryParts['GROUPBY'] = implode(', ', $queryBuilder->getQueryPart('groupBy'));
6390
        $queryParts['ORDERBY'] = implode(', ', $queryBuilder->getQueryPart('orderBy'));
6391
        if ($queryBuilder->getFirstResult() > 0) {
6392
            $queryParts['LIMIT'] = $queryBuilder->getFirstResult() . ',' . $queryBuilder->getMaxResults();
6393
        } elseif ($queryBuilder->getMaxResults() > 0) {
6394
            $queryParts['LIMIT'] = $queryBuilder->getMaxResults();
6395
        }
6396
6397
        return $queryParts;
6398
    }
6399
6400
    /**
6401
     * Helper to transform the QueryBuilder join part into a SQL fragment.
6402
     *
6403
     * @param string $fromAlias
6404
     * @param array $joinParts
6405
     * @param array $knownAliases
6406
     * @return string
6407
     * @throws \RuntimeException
6408
     */
6409
    protected function getQueryArrayJoinHelper(string $fromAlias, array $joinParts, array &$knownAliases): string
6410
    {
6411
        $sql = '';
6412
6413
        if (isset($joinParts['join'][$fromAlias])) {
6414
            foreach ($joinParts['join'][$fromAlias] as $join) {
6415
                if (array_key_exists($join['joinAlias'], $knownAliases)) {
6416
                    throw new \RuntimeException(
6417
                        'Non unique join alias: "' . $join['joinAlias'] . '" found.',
6418
                        1472748872
6419
                    );
6420
                }
6421
                $sql .= ' ' . strtoupper($join['joinType'])
6422
                    . ' JOIN ' . $join['joinTable'] . ' ' . $join['joinAlias']
6423
                    . ' ON ' . ((string)$join['joinCondition']);
6424
                $knownAliases[$join['joinAlias']] = true;
6425
            }
6426
6427
            foreach ($joinParts['join'][$fromAlias] as $join) {
6428
                $sql .= $this->getQueryArrayJoinHelper($join['joinAlias'], $joinParts, $knownAliases);
6429
            }
6430
        }
6431
6432
        return $sql;
6433
    }
6434
    /**
6435
     * Helper function for getQuery(), creating the WHERE clause of the SELECT query
6436
     *
6437
     * @param string $table The table name
6438
     * @param array $conf The TypoScript configuration properties
6439
     * @return array Associative array containing the prepared data for WHERE, ORDER BY and GROUP BY fragments
6440
     * @throws \InvalidArgumentException
6441
     * @see getQuery()
6442
     */
6443
    protected function getQueryConstraints(string $table, array $conf): array
6444
    {
6445
        // Init:
6446
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
6447
        $expressionBuilder = $queryBuilder->expr();
6448
        $tsfe = $this->getTypoScriptFrontendController();
6449
        $constraints = [];
6450
        $pid_uid_flag = 0;
6451
        $enableFieldsIgnore = [];
6452
        $queryParts = [
6453
            'where' => null,
6454
            'groupBy' => null,
6455
            'orderBy' => null,
6456
        ];
6457
6458
        $isInWorkspace = GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('workspace', 'isOffline');
6459
        $considerMovePlaceholders = (
6460
            $isInWorkspace && $table !== 'pages'
6461
            && !empty($GLOBALS['TCA'][$table]['ctrl']['versioningWS'])
6462
        );
6463
6464
        if (trim($conf['uidInList'])) {
6465
            $listArr = GeneralUtility::intExplode(',', str_replace('this', $tsfe->contentPid, $conf['uidInList']));
6466
6467
            // If move placeholder shall be considered, select via t3ver_move_id
6468
            if ($considerMovePlaceholders) {
6469
                $constraints[] = (string)$expressionBuilder->orX(
6470
                    $expressionBuilder->in($table . '.uid', $listArr),
6471
                    $expressionBuilder->andX(
6472
                        $expressionBuilder->eq(
6473
                            $table . '.t3ver_state',
6474
                            (int)(string)VersionState::cast(VersionState::MOVE_PLACEHOLDER)
6475
                        ),
6476
                        $expressionBuilder->in($table . '.t3ver_move_id', $listArr)
6477
                    )
6478
                );
6479
            } else {
6480
                $constraints[] = (string)$expressionBuilder->in($table . '.uid', $listArr);
6481
            }
6482
            $pid_uid_flag++;
6483
        }
6484
6485
        // Static_* tables are allowed to be fetched from root page
6486
        if (strpos($table, 'static_') === 0) {
6487
            $pid_uid_flag++;
6488
        }
6489
6490
        if (trim($conf['pidInList'])) {
6491
            $listArr = GeneralUtility::intExplode(',', str_replace('this', $tsfe->contentPid, $conf['pidInList']));
6492
            // Removes all pages which are not visible for the user!
6493
            $listArr = $this->checkPidArray($listArr);
6494
            if (GeneralUtility::inList($conf['pidInList'], 'root')) {
6495
                $listArr[] = 0;
6496
            }
6497
            if (GeneralUtility::inList($conf['pidInList'], '-1')) {
6498
                $listArr[] = -1;
6499
                $enableFieldsIgnore['pid'] = true;
6500
            }
6501
            if (!empty($listArr)) {
6502
                $constraints[] = $expressionBuilder->in($table . '.pid', array_map('intval', $listArr));
6503
                $pid_uid_flag++;
6504
            } else {
6505
                // If not uid and not pid then uid is set to 0 - which results in nothing!!
6506
                $pid_uid_flag = 0;
6507
            }
6508
        }
6509
6510
        // If not uid and not pid then uid is set to 0 - which results in nothing!!
6511
        if (!$pid_uid_flag) {
6512
            $constraints[] = $expressionBuilder->eq($table . '.uid', 0);
6513
        }
6514
6515
        $where = isset($conf['where.']) ? trim($this->stdWrap($conf['where'], $conf['where.'])) : trim($conf['where']);
6516
        if ($where) {
6517
            $constraints[] = QueryHelper::stripLogicalOperatorPrefix($where);
6518
        }
6519
6520
        // Check if the default language should be fetched (= doing overlays), or if only the records of a language should be fetched
6521
        // but only do this for TCA tables that have languages enabled
6522
        $languageConstraint = $this->getLanguageRestriction($expressionBuilder, $table, $conf, GeneralUtility::makeInstance(Context::class));
6523
        if ($languageConstraint !== null) {
6524
            $constraints[] = $languageConstraint;
6525
        }
6526
6527
        // Enablefields
6528
        if ($table === 'pages') {
6529
            $constraints[] = QueryHelper::stripLogicalOperatorPrefix($tsfe->sys_page->where_hid_del);
6530
            $constraints[] = QueryHelper::stripLogicalOperatorPrefix($tsfe->sys_page->where_groupAccess);
6531
        } else {
6532
            $constraints[] = QueryHelper::stripLogicalOperatorPrefix($tsfe->sys_page->enableFields($table, -1, $enableFieldsIgnore));
6533
        }
6534
6535
        // MAKE WHERE:
6536
        if (count($constraints) !== 0) {
6537
            $queryParts['where'] = $expressionBuilder->andX(...$constraints);
6538
        }
6539
        // GROUP BY
6540
        if (trim($conf['groupBy'])) {
6541
            $groupBy = isset($conf['groupBy.'])
6542
                ? trim($this->stdWrap($conf['groupBy'], $conf['groupBy.']))
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
6543
                : trim($conf['groupBy']);
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
6544
            $queryParts['groupBy'] = QueryHelper::parseGroupBy($groupBy);
6545
        }
6546
6547
        // ORDER BY
6548
        if (trim($conf['orderBy'])) {
6549
            $orderByString = isset($conf['orderBy.'])
6550
                ? trim($this->stdWrap($conf['orderBy'], $conf['orderBy.']))
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
6551
                : trim($conf['orderBy']);
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
6552
6553
            $queryParts['orderBy'] = QueryHelper::parseOrderBy($orderByString);
6554
        }
6555
6556
        // Return result:
6557
        return $queryParts;
6558
    }
6559
6560
    /**
6561
     * Adds parts to the WHERE clause that are related to language.
6562
     * This only works on TCA tables which have the [ctrl][languageField] field set or if they
6563
     * have select.languageField = my_language_field set explicitly.
6564
     *
6565
     * It is also possible to disable the language restriction for a query by using select.languageField = 0,
6566
     * if select.languageField is not explicitly set, the TCA default values are taken.
6567
     *
6568
     * If the table is "localizeable" (= any of the criteria above is met), then the DB query is restricted:
6569
     *
6570
     * If the current language aspect has overlays enabled, then the only records with language "0" or "-1" are
6571
     * fetched (the overlays are taken care of later-on).
6572
     * if the current language has overlays but also records without localization-parent (free mode) available,
6573
     * then these are fetched as well. This can explicitly set via select.includeRecordsWithoutDefaultTranslation = 1
6574
     * which overrules the overlayType within the language aspect.
6575
     *
6576
     * If the language aspect has NO overlays enabled, it behaves as in "free mode" (= only fetch the records
6577
     * for the current language.
6578
     *
6579
     * @param ExpressionBuilder $expressionBuilder
6580
     * @param string $table
6581
     * @param array $conf
6582
     * @param Context $context
6583
     * @return string|\TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression|null
6584
     * @throws \TYPO3\CMS\Core\Context\Exception\AspectNotFoundException
6585
     */
6586
    protected function getLanguageRestriction(ExpressionBuilder $expressionBuilder, string $table, array $conf, Context $context)
6587
    {
6588
        $languageField = '';
6589
        $localizationParentField = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] ?? null;
6590
        // Check if the table is translatable, and set the language field by default from the TCA information
6591
        if (!empty($conf['languageField']) || !isset($conf['languageField'])) {
6592
            if (isset($conf['languageField']) && !empty($GLOBALS['TCA'][$table]['columns'][$conf['languageField']])) {
6593
                $languageField = $conf['languageField'];
6594
            } elseif (!empty($GLOBALS['TCA'][$table]['ctrl']['languageField']) && !empty($localizationParentField)) {
6595
                $languageField = $table . '.' . $GLOBALS['TCA'][$table]['ctrl']['languageField'];
6596
            }
6597
        }
6598
6599
        // No language restriction enabled explicitly or available via TCA
6600
        if (empty($languageField)) {
6601
            return null;
6602
        }
6603
6604
        /** @var LanguageAspect $languageAspect */
6605
        $languageAspect = $context->getAspect('language');
6606
        if ($languageAspect->doOverlays() && !empty($localizationParentField)) {
6607
            // Sys language content is set to zero/-1 - and it is expected that whatever routine processes the output will
6608
            // OVERLAY the records with localized versions!
6609
            $languageQuery = $expressionBuilder->in($languageField, [0, -1]);
6610
            // Use this option to include records that don't have a default language counterpart ("free mode")
6611
            // (originalpointerfield is 0 and the language field contains the requested language)
6612
            if (isset($conf['includeRecordsWithoutDefaultTranslation']) || $conf['includeRecordsWithoutDefaultTranslation.']) {
6613
                $includeRecordsWithoutDefaultTranslation = isset($conf['includeRecordsWithoutDefaultTranslation.']) ?
6614
                    $this->stdWrap($conf['includeRecordsWithoutDefaultTranslation'], $conf['includeRecordsWithoutDefaultTranslation.']) : $conf['includeRecordsWithoutDefaultTranslation'];
6615
                $includeRecordsWithoutDefaultTranslation = trim($includeRecordsWithoutDefaultTranslation) !== '';
6616
            } else {
6617
                // Option was not explicitly set, check what's in for the language overlay type.
6618
                $includeRecordsWithoutDefaultTranslation = $languageAspect->getOverlayType() === $languageAspect::OVERLAYS_ON_WITH_FLOATING;
6619
            }
6620
            if ($includeRecordsWithoutDefaultTranslation) {
6621
                $languageQuery = $expressionBuilder->orX(
6622
                    $languageQuery,
6623
                    $expressionBuilder->andX(
6624
                        $expressionBuilder->eq($table . '.' . $localizationParentField, 0),
6625
                        $expressionBuilder->eq($languageField, $languageAspect->getContentId())
6626
                    )
6627
                );
6628
            }
6629
            return $languageQuery;
6630
        }
6631
        // No overlays = only fetch records given for the requested language and "all languages"
6632
        return $expressionBuilder->in($languageField, [$languageAspect->getContentId(), -1]);
6633
    }
6634
6635
    /**
6636
     * Helper function for getQuery, sanitizing the select part
6637
     *
6638
     * This functions checks if the necessary fields are part of the select
6639
     * and adds them if necessary.
6640
     *
6641
     * @param string $selectPart Select part
6642
     * @param string $table Table to select from
6643
     * @return string Sanitized select part
6644
     * @internal
6645
     * @see getQuery
6646
     */
6647
    protected function sanitizeSelectPart($selectPart, $table)
6648
    {
6649
        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
6650
6651
        // Pattern matching parts
6652
        $matchStart = '/(^\\s*|,\\s*|' . $table . '\\.)';
6653
        $matchEnd = '(\\s*,|\\s*$)/';
6654
        $necessaryFields = ['uid', 'pid'];
6655
        $wsFields = ['t3ver_state'];
6656
        if (isset($GLOBALS['TCA'][$table]) && !preg_match($matchStart . '\\*' . $matchEnd, $selectPart) && !preg_match('/(count|max|min|avg|sum)\\([^\\)]+\\)|distinct/i', $selectPart)) {
6657
            foreach ($necessaryFields as $field) {
6658
                $match = $matchStart . $field . $matchEnd;
6659
                if (!preg_match($match, $selectPart)) {
6660
                    $selectPart .= ', ' . $connection->quoteIdentifier($table . '.' . $field) . ' AS ' . $connection->quoteIdentifier($field);
6661
                }
6662
            }
6663
            if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
6664
                foreach ($wsFields as $field) {
6665
                    $match = $matchStart . $field . $matchEnd;
6666
                    if (!preg_match($match, $selectPart)) {
6667
                        $selectPart .= ', ' . $connection->quoteIdentifier($table . '.' . $field) . ' AS ' . $connection->quoteIdentifier($field);
6668
                    }
6669
                }
6670
            }
6671
        }
6672
        return $selectPart;
6673
    }
6674
6675
    /**
6676
     * 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)
6677
     *
6678
     * @param array $listArr Array of Page UID numbers for select and for which pages with enablefields and bad doktypes should be removed.
6679
     * @return array Returns the array of remaining page UID numbers
6680
     * @internal
6681
     * @see checkPid()
6682
     */
6683
    public function checkPidArray($listArr)
6684
    {
6685
        if (!is_array($listArr) || empty($listArr)) {
0 ignored issues
show
introduced by
The condition is_array($listArr) is always true.
Loading history...
6686
            return [];
6687
        }
6688
        $outArr = [];
6689
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
6690
        $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
6691
        $queryBuilder->select('uid')
6692
            ->from('pages')
6693
            ->where(
6694
                $queryBuilder->expr()->in(
6695
                    'uid',
6696
                    $queryBuilder->createNamedParameter($listArr, Connection::PARAM_INT_ARRAY)
6697
                ),
6698
                $queryBuilder->expr()->notIn(
6699
                    'doktype',
6700
                    $queryBuilder->createNamedParameter(
6701
                        GeneralUtility::intExplode(',', $this->checkPid_badDoktypeList, true),
6702
                        Connection::PARAM_INT_ARRAY
6703
                    )
6704
                )
6705
            );
6706
        try {
6707
            $result = $queryBuilder->execute();
6708
            while ($row = $result->fetch()) {
6709
                $outArr[] = $row['uid'];
6710
            }
6711
        } catch (DBALException $e) {
6712
            $this->getTimeTracker()->setTSlogMessage($e->getMessage() . ': ' . $queryBuilder->getSQL(), 3);
6713
        }
6714
6715
        return $outArr;
6716
    }
6717
6718
    /**
6719
     * Checks if a page UID is available due to enableFields() AND the list of bad doktype numbers ($this->checkPid_badDoktypeList)
6720
     *
6721
     * @param int $uid Page UID to test
6722
     * @return bool TRUE if OK
6723
     * @internal
6724
     * @see checkPidArray()
6725
     */
6726
    public function checkPid($uid)
6727
    {
6728
        $uid = (int)$uid;
6729
        if (!isset($this->checkPid_cache[$uid])) {
6730
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
6731
            $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
6732
            $count = $queryBuilder->count('*')
6733
                ->from('pages')
6734
                ->where(
6735
                    $queryBuilder->expr()->eq(
6736
                        'uid',
6737
                        $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
6738
                    ),
6739
                    $queryBuilder->expr()->notIn(
6740
                        'doktype',
6741
                        $queryBuilder->createNamedParameter(
6742
                            GeneralUtility::intExplode(',', $this->checkPid_badDoktypeList, true),
6743
                            Connection::PARAM_INT_ARRAY
6744
                        )
6745
                    )
6746
                )
6747
                ->execute()
6748
                ->fetchColumn(0);
6749
6750
            $this->checkPid_cache[$uid] = (bool)$count;
6751
        }
6752
        return $this->checkPid_cache[$uid];
6753
    }
6754
6755
    /**
6756
     * Builds list of marker values for handling PDO-like parameter markers in select parts.
6757
     * 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.
6758
     *
6759
     * @param string $table Table to select records from
6760
     * @param array $conf Select part of CONTENT definition
6761
     * @return array List of values to replace markers with
6762
     * @internal
6763
     * @see getQuery()
6764
     */
6765
    public function getQueryMarkers($table, $conf)
6766
    {
6767
        if (!is_array($conf['markers.'])) {
6768
            return [];
6769
        }
6770
        // Parse markers and prepare their values
6771
        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
6772
        $markerValues = [];
6773
        foreach ($conf['markers.'] as $dottedMarker => $dummy) {
6774
            $marker = rtrim($dottedMarker, '.');
6775
            if ($dottedMarker != $marker . '.') {
6776
                continue;
6777
            }
6778
            // Parse definition
6779
            $tempValue = isset($conf['markers.'][$dottedMarker])
6780
                ? $this->stdWrap($conf['markers.'][$dottedMarker]['value'], $conf['markers.'][$dottedMarker])
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
6781
                : $conf['markers.'][$dottedMarker]['value'];
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
6782
            // Quote/escape if needed
6783
            if (is_numeric($tempValue)) {
6784
                if ((int)$tempValue == $tempValue) {
6785
                    // Handle integer
6786
                    $markerValues[$marker] = (int)$tempValue;
6787
                } else {
6788
                    // Handle float
6789
                    $markerValues[$marker] = (float)$tempValue;
6790
                }
6791
            } elseif ($tempValue === null) {
6792
                // It represents NULL
6793
                $markerValues[$marker] = 'NULL';
6794
            } elseif (!empty($conf['markers.'][$dottedMarker]['commaSeparatedList'])) {
6795
                // See if it is really a comma separated list of values
6796
                $explodeValues = GeneralUtility::trimExplode(',', $tempValue);
6797
                if (count($explodeValues) > 1) {
6798
                    // Handle each element of list separately
6799
                    $tempArray = [];
6800
                    foreach ($explodeValues as $listValue) {
6801
                        if (is_numeric($listValue)) {
6802
                            if ((int)$listValue == $listValue) {
6803
                                $tempArray[] = (int)$listValue;
6804
                            } else {
6805
                                $tempArray[] = (float)$listValue;
6806
                            }
6807
                        } else {
6808
                            // If quoted, remove quotes before
6809
                            // escaping.
6810
                            if (preg_match('/^\'([^\']*)\'$/', $listValue, $matches)) {
6811
                                $listValue = $matches[1];
6812
                            } elseif (preg_match('/^\\"([^\\"]*)\\"$/', $listValue, $matches)) {
6813
                                $listValue = $matches[1];
6814
                            }
6815
                            $tempArray[] = $connection->quote($listValue);
6816
                        }
6817
                    }
6818
                    $markerValues[$marker] = implode(',', $tempArray);
6819
                } else {
6820
                    // Handle remaining values as string
6821
                    $markerValues[$marker] = $connection->quote($tempValue);
6822
                }
6823
            } else {
6824
                // Handle remaining values as string
6825
                $markerValues[$marker] = $connection->quote($tempValue);
6826
            }
6827
        }
6828
        return $markerValues;
6829
    }
6830
6831
    /***********************************************
6832
     *
6833
     * Frontend editing functions
6834
     *
6835
     ***********************************************/
6836
    /**
6837
     * 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.
6838
     * With the "edit panel" the user will see buttons with links to editing, moving, hiding, deleting the element
6839
     * This function is used for the cObject EDITPANEL and the stdWrap property ".editPanel"
6840
     *
6841
     * @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.
6842
     * @param array $conf TypoScript configuration properties for the editPanel
6843
     * @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
6844
     * @param array $dataArray Alternative data array to use. Default is $this->data
6845
     * @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.
6846
     */
6847
    public function editPanel($content, $conf, $currentRecord = '', $dataArray = [])
6848
    {
6849
        if (!$this->getTypoScriptFrontendController()->isBackendUserLoggedIn()) {
6850
            return $content;
6851
        }
6852
        if (!$this->getTypoScriptFrontendController()->displayEditIcons) {
6853
            return $content;
6854
        }
6855
6856
        if (!$currentRecord) {
6857
            $currentRecord = $this->currentRecord;
6858
        }
6859
        if (empty($dataArray)) {
6860
            $dataArray = $this->data;
6861
        }
6862
6863
        if ($conf['newRecordFromTable']) {
6864
            $currentRecord = $conf['newRecordFromTable'] . ':NEW';
6865
            $conf['allow'] = 'new';
6866
            $checkEditAccessInternals = false;
6867
        } else {
6868
            $checkEditAccessInternals = true;
6869
        }
6870
        [$table, $uid] = explode(':', $currentRecord);
6871
        // Page ID for new records, 0 if not specified
6872
        $newRecordPid = (int)$conf['newRecordInPid'];
6873
        $newUid = null;
6874
        if (!$conf['onlyCurrentPid'] || $dataArray['pid'] == $this->getTypoScriptFrontendController()->id) {
6875
            if ($table === 'pages') {
6876
                $newUid = $uid;
6877
            } else {
6878
                if ($conf['newRecordFromTable']) {
6879
                    $newUid = $this->getTypoScriptFrontendController()->id;
6880
                    if ($newRecordPid) {
6881
                        $newUid = $newRecordPid;
6882
                    }
6883
                } else {
6884
                    $newUid = -1 * $uid;
6885
                }
6886
            }
6887
        }
6888
        if ($table && $this->getFrontendBackendUser()->allowedToEdit($table, $dataArray, $conf, $checkEditAccessInternals) && $this->getFrontendBackendUser()->allowedToEditLanguage($table, $dataArray)) {
6889
            $editClass = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/classes/class.frontendedit.php']['edit'];
6890
            if ($editClass) {
6891
                $edit = GeneralUtility::makeInstance($editClass);
6892
                $allowedActions = $this->getFrontendBackendUser()->getAllowedEditActions($table, $conf, $dataArray['pid']);
6893
                $content = $edit->editPanel($content, $conf, $currentRecord, $dataArray, $table, $allowedActions, $newUid, []);
6894
            }
6895
        }
6896
        return $content;
6897
    }
6898
6899
    /**
6900
     * 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.
6901
     * This implements TYPO3 context sensitive editing facilities. Only backend users will have access (if properly configured as well).
6902
     *
6903
     * @param string $content The content to which the edit icons should be appended
6904
     * @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
6905
     * @param array $conf TypoScript properties for configuring the edit icons.
6906
     * @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
6907
     * @param array $dataArray Alternative data array to use. Default is $this->data
6908
     * @param string $addUrlParamStr Additional URL parameters for the link pointing to FormEngine
6909
     * @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.
6910
     */
6911
    public function editIcons($content, $params, array $conf = [], $currentRecord = '', $dataArray = [], $addUrlParamStr = '')
6912
    {
6913
        if (!$this->getTypoScriptFrontendController()->isBackendUserLoggedIn()) {
6914
            return $content;
6915
        }
6916
        if (!$this->getTypoScriptFrontendController()->displayFieldEditIcons) {
6917
            return $content;
6918
        }
6919
        if (!$currentRecord) {
6920
            $currentRecord = $this->currentRecord;
6921
        }
6922
        if (empty($dataArray)) {
6923
            $dataArray = $this->data;
6924
        }
6925
        // Check incoming params:
6926
        [$currentRecordTable, $currentRecordUID] = explode(':', $currentRecord);
6927
        [$fieldList, $table] = array_reverse(GeneralUtility::trimExplode(':', $params, true));
6928
        // Reverse the array because table is optional
6929
        if (!$table) {
6930
            $table = $currentRecordTable;
6931
        } elseif ($table != $currentRecordTable) {
6932
            // If the table is set as the first parameter, and does not match the table of the current record, then just return.
6933
            return $content;
6934
        }
6935
6936
        $editUid = $dataArray['_LOCALIZED_UID'] ?: $currentRecordUID;
6937
        // Edit icons imply that the editing action is generally allowed, assuming page and content element permissions permit it.
6938
        if (!array_key_exists('allow', $conf)) {
6939
            $conf['allow'] = 'edit';
6940
        }
6941
        if ($table && $this->getFrontendBackendUser()->allowedToEdit($table, $dataArray, $conf, true) && $fieldList && $this->getFrontendBackendUser()->allowedToEditLanguage($table, $dataArray)) {
6942
            $editClass = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/classes/class.frontendedit.php']['edit'];
6943
            if ($editClass) {
6944
                $edit = GeneralUtility::makeInstance($editClass);
6945
                $content = $edit->editIcons($content, $params, $conf, $currentRecord, $dataArray, $addUrlParamStr, $table, $editUid, $fieldList);
6946
            }
6947
        }
6948
        return $content;
6949
    }
6950
6951
    /**
6952
     * Returns TRUE if the input table/row would be hidden in the frontend (according nto the current time and simulate user group)
6953
     *
6954
     * @param string $table The table name
6955
     * @param array $row The data record
6956
     * @return bool
6957
     * @internal
6958
     * @see editPanelPreviewBorder()
6959
     */
6960
    public function isDisabled($table, $row)
6961
    {
6962
        $tsfe = $this->getTypoScriptFrontendController();
6963
        $enablecolumns = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns'];
6964
        return $enablecolumns['disabled'] && $row[$enablecolumns['disabled']]
6965
            || $enablecolumns['fe_group'] && $tsfe->simUserGroup && (int)$row[$enablecolumns['fe_group']] === (int)$tsfe->simUserGroup
6966
            || $enablecolumns['starttime'] && $row[$enablecolumns['starttime']] > $GLOBALS['EXEC_TIME']
6967
            || $enablecolumns['endtime'] && $row[$enablecolumns['endtime']] && $row[$enablecolumns['endtime']] < $GLOBALS['EXEC_TIME'];
6968
    }
6969
6970
    /**
6971
     * Get instance of FAL resource factory
6972
     *
6973
     * @return ResourceFactory
6974
     */
6975
    protected function getResourceFactory()
6976
    {
6977
        return GeneralUtility::makeInstance(ResourceFactory::class);
6978
    }
6979
6980
    /**
6981
     * Wrapper function for GeneralUtility::getIndpEnv()
6982
     *
6983
     * @see GeneralUtility::getIndpEnv
6984
     * @param string $key Name of the "environment variable"/"server variable" you wish to get.
6985
     * @return string
6986
     */
6987
    protected function getEnvironmentVariable($key)
6988
    {
6989
        return GeneralUtility::getIndpEnv($key);
6990
    }
6991
6992
    /**
6993
     * Fetches content from cache
6994
     *
6995
     * @param array $configuration Array
6996
     * @return string|bool FALSE on cache miss
6997
     * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
6998
     */
6999
    protected function getFromCache(array $configuration)
7000
    {
7001
        $content = false;
7002
7003
        $cacheKey = $this->calculateCacheKey($configuration);
7004
        if (!empty($cacheKey)) {
7005
            /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cacheFrontend */
7006
            $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)
7007
                ->getCache('hash');
7008
            $content = $cacheFrontend->get($cacheKey);
7009
        }
7010
        return $content;
7011
    }
7012
7013
    /**
7014
     * Calculates the lifetime of a cache entry based on the given configuration
7015
     *
7016
     * @param array $configuration
7017
     * @return int|null
7018
     */
7019
    protected function calculateCacheLifetime(array $configuration)
7020
    {
7021
        $lifetimeConfiguration = $configuration['lifetime'] ?? '';
7022
        $lifetimeConfiguration = isset($configuration['lifetime.'])
7023
            ? $this->stdWrap($lifetimeConfiguration, $configuration['lifetime.'])
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
7024
            : $lifetimeConfiguration;
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
7025
7026
        $lifetime = null; // default lifetime
7027
        if (strtolower($lifetimeConfiguration) === 'unlimited') {
7028
            $lifetime = 0; // unlimited
7029
        } elseif ($lifetimeConfiguration > 0) {
7030
            $lifetime = (int)$lifetimeConfiguration; // lifetime in seconds
7031
        }
7032
        return $lifetime;
7033
    }
7034
7035
    /**
7036
     * Calculates the tags for a cache entry bases on the given configuration
7037
     *
7038
     * @param array $configuration
7039
     * @return array
7040
     */
7041
    protected function calculateCacheTags(array $configuration)
7042
    {
7043
        $tags = $configuration['tags'] ?? '';
7044
        $tags = isset($configuration['tags.'])
7045
            ? $this->stdWrap($tags, $configuration['tags.'])
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
7046
            : $tags;
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
7047
        return empty($tags) ? [] : GeneralUtility::trimExplode(',', $tags);
7048
    }
7049
7050
    /**
7051
     * Applies stdWrap to the cache key
7052
     *
7053
     * @param array $configuration
7054
     * @return string
7055
     */
7056
    protected function calculateCacheKey(array $configuration)
7057
    {
7058
        $key = $configuration['key'] ?? '';
7059
        return isset($configuration['key.'])
7060
            ? $this->stdWrap($key, $configuration['key.'])
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
7061
            : $key;
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
7062
    }
7063
7064
    /**
7065
     * Returns the current BE user.
7066
     *
7067
     * @return \TYPO3\CMS\Backend\FrontendBackendUserAuthentication
7068
     */
7069
    protected function getFrontendBackendUser()
7070
    {
7071
        return $GLOBALS['BE_USER'];
7072
    }
7073
7074
    /**
7075
     * @return TimeTracker
7076
     */
7077
    protected function getTimeTracker()
7078
    {
7079
        return GeneralUtility::makeInstance(TimeTracker::class);
7080
    }
7081
7082
    /**
7083
     * @return \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
7084
     */
7085
    protected function getTypoScriptFrontendController()
7086
    {
7087
        return $this->typoScriptFrontendController ?: $GLOBALS['TSFE'];
7088
    }
7089
7090
    /**
7091
     * Support anchors without href value
7092
     * Changes ContentObjectRenderer::typolink to render a tag without href,
7093
     * if id or name attribute is present.
7094
     *
7095
     * @param string $linkText
7096
     * @param array $conf Typolink configuration decoded as array
7097
     * @return string Full a-Tag or just the linktext if id or name are not set.
7098
     */
7099
    protected function resolveAnchorLink(string $linkText, array $conf): string
7100
    {
7101
        $anchorTag = '<a ' . $this->getATagParams($conf) . '>';
7102
        $aTagParams = GeneralUtility::get_tag_attributes($anchorTag);
7103
        // If it looks like a anchor tag, render it anyway
7104
        if (isset($aTagParams['id']) || isset($aTagParams['name'])) {
7105
            return $anchorTag . $linkText . '</a>';
7106
        }
7107
        // Otherwise just return the link text
7108
        return $linkText;
7109
    }
7110
7111
    /**
7112
     * Get content length of the current tag that could also contain nested tag contents
7113
     *
7114
     * @param string $theValue
7115
     * @param int $pointer
7116
     * @param string $currentTag
7117
     * @return int
7118
     */
7119
    protected function getContentLengthOfCurrentTag(string $theValue, int $pointer, string $currentTag): int
7120
    {
7121
        $tempContent = strtolower(substr($theValue, $pointer));
7122
        $startTag = '<' . $currentTag;
7123
        $endTag = '</' . $currentTag . '>';
7124
        $offsetCount = 0;
7125
7126
        // Take care for nested tags
7127
        do {
7128
            $nextMatchingEndTagPosition = strpos($tempContent, $endTag);
7129
            $nextSameTypeTagPosition = strpos($tempContent, $startTag);
7130
7131
            // filter out nested tag contents to help getting the correct closing tag
7132
            if ($nextSameTypeTagPosition !== false && $nextSameTypeTagPosition < $nextMatchingEndTagPosition) {
7133
                $lastOpeningTagStartPosition = strrpos(substr($tempContent, 0, $nextMatchingEndTagPosition), $startTag);
7134
                $closingTagEndPosition = $nextMatchingEndTagPosition + strlen($endTag);
7135
                $offsetCount += $closingTagEndPosition - $lastOpeningTagStartPosition;
7136
7137
                // replace content from latest tag start to latest tag end
7138
                $tempContent = substr($tempContent, 0, $lastOpeningTagStartPosition) . substr($tempContent, $closingTagEndPosition);
7139
            }
7140
        } while (
7141
            ($nextMatchingEndTagPosition !== false && $nextSameTypeTagPosition !== false) &&
7142
            $nextSameTypeTagPosition < $nextMatchingEndTagPosition
7143
        );
7144
7145
        // if no closing tag is found we use length of the whole content
7146
        $endingOffset = strlen($tempContent);
7147
        if ($nextMatchingEndTagPosition !== false) {
7148
            $endingOffset = $nextMatchingEndTagPosition + $offsetCount;
7149
        }
7150
7151
        return $endingOffset;
7152
    }
7153
}
7154