Completed
Push — master ( ad01e4...0babc9 )
by
unknown
16:35
created

ContentObjectRenderer::getFrontendBackendUser()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
namespace TYPO3\CMS\Frontend\ContentObject;
3
4
/*
5
 * This file is part of the TYPO3 CMS project.
6
 *
7
 * It is free software; you can redistribute it and/or modify it under
8
 * the terms of the GNU General Public License, either version 2
9
 * of the License, or any later version.
10
 *
11
 * For the full copyright and license information, please read the
12
 * LICENSE.txt file that was distributed with this source code.
13
 *
14
 * The TYPO3 project - inspiring people to share!
15
 */
16
17
use Doctrine\DBAL\DBALException;
18
use Doctrine\DBAL\Driver\Statement;
19
use 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\ConnectionPool;
29
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
30
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
31
use TYPO3\CMS\Core\Database\Query\QueryHelper;
32
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
33
use TYPO3\CMS\Core\Database\Query\Restriction\DocumentTypeExclusionRestriction;
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\Exception\UnknownLinkHandlerException;
40
use TYPO3\CMS\Core\LinkHandling\LinkService;
41
use TYPO3\CMS\Core\Log\LogManager;
42
use TYPO3\CMS\Core\Mail\MailMessage;
43
use TYPO3\CMS\Core\Page\AssetCollector;
44
use TYPO3\CMS\Core\Resource\Exception;
45
use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
46
use TYPO3\CMS\Core\Resource\File;
47
use TYPO3\CMS\Core\Resource\FileInterface;
48
use TYPO3\CMS\Core\Resource\FileReference;
49
use TYPO3\CMS\Core\Resource\ProcessedFile;
50
use TYPO3\CMS\Core\Resource\ResourceFactory;
51
use TYPO3\CMS\Core\Service\DependencyOrderingService;
52
use TYPO3\CMS\Core\Service\FlexFormService;
53
use TYPO3\CMS\Core\Service\MarkerBasedTemplateService;
54
use TYPO3\CMS\Core\Site\SiteFinder;
55
use TYPO3\CMS\Core\TimeTracker\TimeTracker;
56
use TYPO3\CMS\Core\Type\BitSet;
57
use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
58
use TYPO3\CMS\Core\TypoScript\TypoScriptService;
59
use TYPO3\CMS\Core\Utility\ArrayUtility;
60
use TYPO3\CMS\Core\Utility\DebugUtility;
61
use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException;
62
use TYPO3\CMS\Core\Utility\GeneralUtility;
63
use TYPO3\CMS\Core\Utility\HttpUtility;
64
use TYPO3\CMS\Core\Utility\MailUtility;
65
use TYPO3\CMS\Core\Utility\MathUtility;
66
use TYPO3\CMS\Core\Utility\StringUtility;
67
use TYPO3\CMS\Core\Versioning\VersionState;
68
use TYPO3\CMS\Frontend\ContentObject\Exception\ContentRenderingException;
69
use TYPO3\CMS\Frontend\ContentObject\Exception\ExceptionHandlerInterface;
70
use TYPO3\CMS\Frontend\ContentObject\Exception\ProductionExceptionHandler;
71
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
72
use TYPO3\CMS\Frontend\Http\UrlProcessorInterface;
73
use TYPO3\CMS\Frontend\Imaging\GifBuilder;
74
use TYPO3\CMS\Frontend\Page\PageLayoutResolver;
75
use TYPO3\CMS\Frontend\Resource\FilePathSanitizer;
76
use TYPO3\CMS\Frontend\Service\TypoLinkCodecService;
77
use TYPO3\CMS\Frontend\Typolink\AbstractTypolinkBuilder;
78
use TYPO3\CMS\Frontend\Typolink\UnableToLinkException;
79
80
/**
81
 * This class contains all main TypoScript features.
82
 * This includes the rendering of TypoScript content objects (cObjects).
83
 * Is the backbone of TypoScript Template rendering.
84
 *
85
 * There are lots of functions you can use from your include-scripts.
86
 * The class is normally instantiated and referred to as "cObj".
87
 * 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.
88
 */
89
class ContentObjectRenderer implements LoggerAwareInterface
90
{
91
    use LoggerAwareTrait;
92
93
    /**
94
     * @var ContainerInterface
95
     */
96
    protected $container;
97
98
    /**
99
     * @var array
100
     */
101
    public $align = [
102
        'center',
103
        'right',
104
        'left'
105
    ];
106
107
    /**
108
     * stdWrap functions in their correct order
109
     *
110
     * @see stdWrap()
111
     * @var string[]
112
     */
113
    public $stdWrapOrder = [
114
        'stdWrapPreProcess' => 'hook',
115
        // this is a placeholder for the first Hook
116
        'cacheRead' => 'hook',
117
        // this is a placeholder for checking if the content is available in cache
118
        'setContentToCurrent' => 'boolean',
119
        'setContentToCurrent.' => 'array',
120
        'addPageCacheTags' => 'string',
121
        'addPageCacheTags.' => 'array',
122
        'setCurrent' => 'string',
123
        'setCurrent.' => 'array',
124
        'lang.' => 'array',
125
        'data' => 'getText',
126
        'data.' => 'array',
127
        'field' => 'fieldName',
128
        'field.' => 'array',
129
        'current' => 'boolean',
130
        'current.' => 'array',
131
        'cObject' => 'cObject',
132
        'cObject.' => 'array',
133
        'numRows.' => 'array',
134
        'preUserFunc' => 'functionName',
135
        'stdWrapOverride' => 'hook',
136
        // this is a placeholder for the second Hook
137
        'override' => 'string',
138
        'override.' => 'array',
139
        'preIfEmptyListNum' => 'listNum',
140
        'preIfEmptyListNum.' => 'array',
141
        'ifNull' => 'string',
142
        'ifNull.' => 'array',
143
        'ifEmpty' => 'string',
144
        'ifEmpty.' => 'array',
145
        'ifBlank' => 'string',
146
        'ifBlank.' => 'array',
147
        'listNum' => 'listNum',
148
        'listNum.' => 'array',
149
        'trim' => 'boolean',
150
        'trim.' => 'array',
151
        'strPad.' => 'array',
152
        'stdWrap' => 'stdWrap',
153
        'stdWrap.' => 'array',
154
        'stdWrapProcess' => 'hook',
155
        // this is a placeholder for the third Hook
156
        'required' => 'boolean',
157
        'required.' => 'array',
158
        'if.' => 'array',
159
        'fieldRequired' => 'fieldName',
160
        'fieldRequired.' => 'array',
161
        'csConv' => 'string',
162
        'csConv.' => 'array',
163
        'parseFunc' => 'objectpath',
164
        'parseFunc.' => 'array',
165
        'HTMLparser' => 'boolean',
166
        'HTMLparser.' => 'array',
167
        'split.' => 'array',
168
        'replacement.' => 'array',
169
        'prioriCalc' => 'boolean',
170
        'prioriCalc.' => 'array',
171
        'char' => 'integer',
172
        'char.' => 'array',
173
        'intval' => 'boolean',
174
        'intval.' => 'array',
175
        'hash' => 'string',
176
        'hash.' => 'array',
177
        'round' => 'boolean',
178
        'round.' => 'array',
179
        'numberFormat.' => 'array',
180
        'expandList' => 'boolean',
181
        'expandList.' => 'array',
182
        'date' => 'dateconf',
183
        'date.' => 'array',
184
        'strtotime' => 'strtotimeconf',
185
        'strtotime.' => 'array',
186
        'strftime' => 'strftimeconf',
187
        'strftime.' => 'array',
188
        'age' => 'boolean',
189
        'age.' => 'array',
190
        'case' => 'case',
191
        'case.' => 'array',
192
        'bytes' => 'boolean',
193
        'bytes.' => 'array',
194
        'substring' => 'parameters',
195
        'substring.' => 'array',
196
        'cropHTML' => 'crop',
197
        'cropHTML.' => 'array',
198
        'stripHtml' => 'boolean',
199
        'stripHtml.' => 'array',
200
        'crop' => 'crop',
201
        'crop.' => 'array',
202
        'rawUrlEncode' => 'boolean',
203
        'rawUrlEncode.' => 'array',
204
        'htmlSpecialChars' => 'boolean',
205
        'htmlSpecialChars.' => 'array',
206
        'encodeForJavaScriptValue' => 'boolean',
207
        'encodeForJavaScriptValue.' => 'array',
208
        'doubleBrTag' => 'string',
209
        'doubleBrTag.' => 'array',
210
        'br' => 'boolean',
211
        'br.' => 'array',
212
        'brTag' => 'string',
213
        'brTag.' => 'array',
214
        'encapsLines.' => 'array',
215
        'keywords' => 'boolean',
216
        'keywords.' => 'array',
217
        'innerWrap' => 'wrap',
218
        'innerWrap.' => 'array',
219
        'innerWrap2' => 'wrap',
220
        'innerWrap2.' => 'array',
221
        'preCObject' => 'cObject',
222
        'preCObject.' => 'array',
223
        'postCObject' => 'cObject',
224
        'postCObject.' => 'array',
225
        'wrapAlign' => 'align',
226
        'wrapAlign.' => 'array',
227
        'typolink.' => 'array',
228
        'wrap' => 'wrap',
229
        'wrap.' => 'array',
230
        'noTrimWrap' => 'wrap',
231
        'noTrimWrap.' => 'array',
232
        'wrap2' => 'wrap',
233
        'wrap2.' => 'array',
234
        'dataWrap' => 'dataWrap',
235
        'dataWrap.' => 'array',
236
        'prepend' => 'cObject',
237
        'prepend.' => 'array',
238
        'append' => 'cObject',
239
        'append.' => 'array',
240
        'wrap3' => 'wrap',
241
        'wrap3.' => 'array',
242
        'orderedStdWrap' => 'stdWrap',
243
        'orderedStdWrap.' => 'array',
244
        'outerWrap' => 'wrap',
245
        'outerWrap.' => 'array',
246
        'insertData' => 'boolean',
247
        'insertData.' => 'array',
248
        'postUserFunc' => 'functionName',
249
        'postUserFuncInt' => 'functionName',
250
        'prefixComment' => 'string',
251
        'prefixComment.' => 'array',
252
        'editIcons' => 'string',
253
        'editIcons.' => 'array',
254
        'editPanel' => 'boolean',
255
        'editPanel.' => 'array',
256
        'cacheStore' => 'hook',
257
        // this is a placeholder for storing the content in cache
258
        'stdWrapPostProcess' => 'hook',
259
        // this is a placeholder for the last Hook
260
        'debug' => 'boolean',
261
        'debug.' => 'array',
262
        'debugFunc' => 'boolean',
263
        'debugFunc.' => 'array',
264
        'debugData' => 'boolean',
265
        'debugData.' => 'array'
266
    ];
267
268
    /**
269
     * Class names for accordant content object names
270
     *
271
     * @var array
272
     */
273
    protected $contentObjectClassMap = [];
274
275
    /**
276
     * Loaded with the current data-record.
277
     *
278
     * If the instance of this class is used to render records from the database those records are found in this array.
279
     * The function stdWrap has TypoScript properties that fetch field-data from this array.
280
     *
281
     * @var array
282
     * @see start()
283
     */
284
    public $data = [];
285
286
    /**
287
     * @var string
288
     */
289
    protected $table = '';
290
291
    /**
292
     * Used for backup
293
     *
294
     * @var array
295
     */
296
    public $oldData = [];
297
298
    /**
299
     * If this is set with an array before stdWrap, it's used instead of $this->data in the data-property in stdWrap
300
     *
301
     * @var string
302
     */
303
    public $alternativeData = '';
304
305
    /**
306
     * Used by the parseFunc function and is loaded with tag-parameters when parsing tags.
307
     *
308
     * @var array
309
     */
310
    public $parameters = [];
311
312
    /**
313
     * @var string
314
     */
315
    public $currentValKey = 'currentValue_kidjls9dksoje';
316
317
    /**
318
     * This is set to the [table]:[uid] of the record delivered in the $data-array, if the cObjects CONTENT or RECORD is in operation.
319
     * Note that $GLOBALS['TSFE']->currentRecord is set to an equal value but always indicating the latest record rendered.
320
     *
321
     * @var string
322
     */
323
    public $currentRecord = '';
324
325
    /**
326
     * Set in RecordsContentObject and ContentContentObject to the current number of records selected in a query.
327
     *
328
     * @var int
329
     */
330
    public $currentRecordTotal = 0;
331
332
    /**
333
     * Incremented in RecordsContentObject and ContentContentObject before each record rendering.
334
     *
335
     * @var int
336
     */
337
    public $currentRecordNumber = 0;
338
339
    /**
340
     * Incremented in RecordsContentObject and ContentContentObject before each record rendering.
341
     *
342
     * @var int
343
     */
344
    public $parentRecordNumber = 0;
345
346
    /**
347
     * 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.
348
     *
349
     * @var array
350
     */
351
    public $parentRecord = [];
352
353
    /**
354
     * @var string|int
355
     */
356
    public $checkPid_badDoktypeList = PageRepository::DOKTYPE_RECYCLER;
357
358
    /**
359
     * This will be set by typoLink() to the url of the most recent link created.
360
     *
361
     * @var string
362
     */
363
    public $lastTypoLinkUrl = '';
364
365
    /**
366
     * DO. link target.
367
     *
368
     * @var string
369
     */
370
    public $lastTypoLinkTarget = '';
371
372
    /**
373
     * @var array
374
     */
375
    public $lastTypoLinkLD = [];
376
377
    /**
378
     * array that registers rendered content elements (or any table) to make sure they are not rendered recursively!
379
     *
380
     * @var array
381
     */
382
    public $recordRegister = [];
383
384
    /**
385
     * Additionally registered content object types and class names
386
     *
387
     * @var array
388
     * @deprecated - will be removed in TYPO3 v11.0
389
     */
390
    protected $cObjHookObjectsRegistry = [];
391
392
    /**
393
     * @var array
394
     * @deprecated - will be removed in TYPO3 v11.0
395
     */
396
    public $cObjHookObjectsArr = [];
397
398
    /**
399
     * Containing hook objects for stdWrap
400
     *
401
     * @var array
402
     */
403
    protected $stdWrapHookObjects = [];
404
405
    /**
406
     * Containing hook objects for getImgResource
407
     *
408
     * @var array
409
     */
410
    protected $getImgResourceHookObjects;
411
412
    /**
413
     * @var File Current file objects (during iterations over files)
414
     */
415
    protected $currentFile;
416
417
    /**
418
     * Set to TRUE by doConvertToUserIntObject() if USER object wants to become USER_INT
419
     * @var bool
420
     */
421
    public $doConvertToUserIntObject = false;
422
423
    /**
424
     * Indicates current object type. Can hold one of OBJECTTYPE_ constants or FALSE.
425
     * The value is set and reset inside USER() function. Any time outside of
426
     * USER() it is FALSE.
427
     * @var bool
428
     */
429
    protected $userObjectType = false;
430
431
    /**
432
     * @var array
433
     */
434
    protected $stopRendering = [];
435
436
    /**
437
     * @var int
438
     */
439
    protected $stdWrapRecursionLevel = 0;
440
441
    /**
442
     * @var TypoScriptFrontendController|null
443
     */
444
    protected $typoScriptFrontendController;
445
446
    /**
447
     * Indicates that object type is USER.
448
     *
449
     * @see ContentObjectRender::$userObjectType
450
     */
451
    const OBJECTTYPE_USER_INT = 1;
452
    /**
453
     * Indicates that object type is USER.
454
     *
455
     * @see ContentObjectRender::$userObjectType
456
     */
457
    const OBJECTTYPE_USER = 2;
458
459
    /**
460
     * @param TypoScriptFrontendController $typoScriptFrontendController
461
     * @param ContainerInterface $container
462
     */
463
    public function __construct(TypoScriptFrontendController $typoScriptFrontendController = null, ContainerInterface $container = null)
464
    {
465
        $this->typoScriptFrontendController = $typoScriptFrontendController;
466
        $this->contentObjectClassMap = $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'];
467
        $this->container = $container;
468
    }
469
470
    /**
471
     * Prevent several objects from being serialized.
472
     * If currentFile is set, it is either a File or a FileReference object. As the object itself can't be serialized,
473
     * we have store a hash and restore the object in __wakeup()
474
     *
475
     * @return array
476
     */
477
    public function __sleep()
478
    {
479
        $vars = get_object_vars($this);
480
        unset($vars['typoScriptFrontendController'], $vars['logger'], $vars['container']);
481
        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...
482
            $this->currentFile = 'FileReference:' . $this->currentFile->getUid();
483
        } 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...
484
            $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...
485
        } else {
486
            unset($vars['currentFile']);
487
        }
488
        return array_keys($vars);
489
    }
490
491
    /**
492
     * Restore currentFile from hash.
493
     * If currentFile references a File, the identifier equals file identifier.
494
     * If it references a FileReference the identifier equals the uid of the reference.
495
     */
496
    public function __wakeup()
497
    {
498
        if (isset($GLOBALS['TSFE'])) {
499
            $this->typoScriptFrontendController = $GLOBALS['TSFE'];
500
        }
501
        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...
502
            [$objectType, $identifier] = explode(':', $this->currentFile, 2);
503
            try {
504
                if ($objectType === 'File') {
505
                    $this->currentFile = GeneralUtility::makeInstance(ResourceFactory::class)->retrieveFileOrFolderObject($identifier);
506
                } elseif ($objectType === 'FileReference') {
507
                    $this->currentFile = GeneralUtility::makeInstance(ResourceFactory::class)->getFileReferenceObject($identifier);
508
                }
509
            } catch (ResourceDoesNotExistException $e) {
510
                $this->currentFile = null;
511
            }
512
        }
513
        $this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
514
        $this->container = GeneralUtility::getContainer();
515
    }
516
517
    /**
518
     * Allow injecting content object class map.
519
     *
520
     * This method is private API, please use configuration
521
     * $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'] to add new content objects
522
     *
523
     * @internal
524
     * @param array $contentObjectClassMap
525
     */
526
    public function setContentObjectClassMap(array $contentObjectClassMap)
527
    {
528
        $this->contentObjectClassMap = $contentObjectClassMap;
529
    }
530
531
    /**
532
     * Register a single content object name to class name
533
     *
534
     * This method is private API, please use configuration
535
     * $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'] to add new content objects
536
     *
537
     * @param string $className
538
     * @param string $contentObjectName
539
     * @internal
540
     */
541
    public function registerContentObjectClass($className, $contentObjectName)
542
    {
543
        $this->contentObjectClassMap[$contentObjectName] = $className;
544
    }
545
546
    /**
547
     * Class constructor.
548
     * Well, it has to be called manually since it is not a real constructor function.
549
     * 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.
550
     *
551
     * @param array $data The record data that is rendered.
552
     * @param string $table The table that the data record is from.
553
     */
554
    public function start($data, $table = '')
555
    {
556
        $this->data = $data;
557
        $this->table = $table;
558
        $this->currentRecord = $table !== ''
559
            ? $table . ':' . ($this->data['uid'] ?? '')
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
560
            : '';
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
561
        $this->parameters = [];
562
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['cObjTypeAndClass'] ?? [] as $classArr) {
563
            trigger_error('The hook $TYPO3_CONF_VARS[SC_OPTIONS][tslib/class.tslib_content.php][cObjTypeAndClass] for adding custom cObjects will be removed in TYPO3 v11.0. Use a custom cObject which you can register directly instead.', E_USER_DEPRECATED);
564
            $this->cObjHookObjectsRegistry[$classArr[0]] = $classArr[1];
565
        }
566
        $this->stdWrapHookObjects = [];
567
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap'] ?? [] as $className) {
568
            $hookObject = GeneralUtility::makeInstance($className);
569
            if (!$hookObject instanceof ContentObjectStdWrapHookInterface) {
570
                throw new \UnexpectedValueException($className . ' must implement interface ' . ContentObjectStdWrapHookInterface::class, 1195043965);
571
            }
572
            $this->stdWrapHookObjects[] = $hookObject;
573
        }
574
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['postInit'] ?? [] as $className) {
575
            $postInitializationProcessor = GeneralUtility::makeInstance($className);
576
            if (!$postInitializationProcessor instanceof ContentObjectPostInitHookInterface) {
577
                throw new \UnexpectedValueException($className . ' must implement interface ' . ContentObjectPostInitHookInterface::class, 1274563549);
578
            }
579
            $postInitializationProcessor->postProcessContentObjectInitialization($this);
580
        }
581
    }
582
583
    /**
584
     * Returns the current table
585
     *
586
     * @return string
587
     */
588
    public function getCurrentTable()
589
    {
590
        return $this->table;
591
    }
592
593
    /**
594
     * Gets the 'getImgResource' hook objects.
595
     * The first call initializes the accordant objects.
596
     *
597
     * @return array The 'getImgResource' hook objects (if any)
598
     */
599
    protected function getGetImgResourceHookObjects()
600
    {
601
        if (!isset($this->getImgResourceHookObjects)) {
602
            $this->getImgResourceHookObjects = [];
603
            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImgResource'] ?? [] as $className) {
604
                $hookObject = GeneralUtility::makeInstance($className);
605
                if (!$hookObject instanceof ContentObjectGetImageResourceHookInterface) {
606
                    throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetImageResourceHookInterface::class, 1218636383);
607
                }
608
                $this->getImgResourceHookObjects[] = $hookObject;
609
            }
610
        }
611
        return $this->getImgResourceHookObjects;
612
    }
613
614
    /**
615
     * Sets the internal variable parentRecord with information about current record.
616
     * 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.
617
     *
618
     * @param array $data The record array
619
     * @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.
620
     * @internal
621
     */
622
    public function setParent($data, $currentRecord)
623
    {
624
        $this->parentRecord = [
625
            'data' => $data,
626
            'currentRecord' => $currentRecord
627
        ];
628
    }
629
630
    /***********************************************
631
     *
632
     * CONTENT_OBJ:
633
     *
634
     ***********************************************/
635
    /**
636
     * Returns the "current" value.
637
     * 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.
638
     * It's like "load accumulator" in the good old C64 days... basically a "register" you can use as you like.
639
     * 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.
640
     *
641
     * @return mixed The "current" value
642
     */
643
    public function getCurrentVal()
644
    {
645
        return $this->data[$this->currentValKey];
646
    }
647
648
    /**
649
     * Sets the "current" value.
650
     *
651
     * @param mixed $value The variable that you want to set as "current
652
     * @see getCurrentVal()
653
     */
654
    public function setCurrentVal($value)
655
    {
656
        $this->data[$this->currentValKey] = $value;
657
    }
658
659
    /**
660
     * Rendering of a "numerical array" of cObjects from TypoScript
661
     * Will call ->cObjGetSingle() for each cObject found and accumulate the output.
662
     *
663
     * @param array $setup array with cObjects as values.
664
     * @param string $addKey A prefix for the debugging information
665
     * @return string Rendered output from the cObjects in the array.
666
     * @see cObjGetSingle()
667
     */
668
    public function cObjGet($setup, $addKey = '')
669
    {
670
        if (!is_array($setup)) {
0 ignored issues
show
introduced by
The condition is_array($setup) is always true.
Loading history...
671
            return '';
672
        }
673
        $sKeyArray = ArrayUtility::filterAndSortByNumericKeys($setup);
674
        $content = '';
675
        foreach ($sKeyArray as $theKey) {
676
            $theValue = $setup[$theKey];
677
            if ((int)$theKey && strpos($theKey, '.') === false) {
678
                $conf = $setup[$theKey . '.'];
679
                $content .= $this->cObjGetSingle($theValue, $conf, $addKey . $theKey);
680
            }
681
        }
682
        return $content;
683
    }
684
685
    /**
686
     * Renders a content object
687
     *
688
     * @param string $name The content object name, eg. "TEXT" or "USER" or "IMAGE"
689
     * @param array $conf The array with TypoScript properties for the content object
690
     * @param string $TSkey A string label used for the internal debugging tracking.
691
     * @return string cObject output
692
     * @throws \UnexpectedValueException
693
     */
694
    public function cObjGetSingle($name, $conf, $TSkey = '__')
695
    {
696
        $content = '';
697
        // Checking that the function is not called eternally. This is done by interrupting at a depth of 100
698
        $this->getTypoScriptFrontendController()->cObjectDepthCounter--;
699
        if ($this->getTypoScriptFrontendController()->cObjectDepthCounter > 0) {
700
            $timeTracker = $this->getTimeTracker();
701
            $name = trim($name);
702
            if ($timeTracker->LR) {
703
                $timeTracker->push($TSkey, $name);
704
            }
705
            // Checking if the COBJ is a reference to another object. (eg. name of 'some.object =< styles.something')
706
            if (isset($name[0]) && $name[0] === '<') {
707
                $key = trim(substr($name, 1));
708
                $cF = GeneralUtility::makeInstance(TypoScriptParser::class);
709
                // $name and $conf is loaded with the referenced values.
710
                $confOverride = is_array($conf) ? $conf : [];
0 ignored issues
show
introduced by
The condition is_array($conf) is always true.
Loading history...
711
                [$name, $conf] = $cF->getVal($key, $this->getTypoScriptFrontendController()->tmpl->setup);
712
                $conf = array_replace_recursive(is_array($conf) ? $conf : [], $confOverride);
713
                // Getting the cObject
714
                $timeTracker->incStackPointer();
715
                $content .= $this->cObjGetSingle($name, $conf, $key);
716
                $timeTracker->decStackPointer();
717
            } else {
718
                $hooked = false;
719
                // Application defined cObjects
720
                // @deprecated since TYPO3 v10.4 - will be removed in TYPO3 v11.0
721
                if (!empty($this->cObjHookObjectsRegistry[$name])) {
722
                    if (empty($this->cObjHookObjectsArr[$name])) {
723
                        $this->cObjHookObjectsArr[$name] = GeneralUtility::makeInstance($this->cObjHookObjectsRegistry[$name]);
724
                    }
725
                    $hookObj = $this->cObjHookObjectsArr[$name];
726
                    if (method_exists($hookObj, 'cObjGetSingleExt')) {
727
                        $content .= $hookObj->cObjGetSingleExt($name, $conf, $TSkey, $this);
728
                        $hooked = true;
729
                    }
730
                }
731
                if (!$hooked) {
732
                    $contentObject = $this->getContentObject($name);
733
                    if ($contentObject) {
734
                        $content .= $this->render($contentObject, $conf);
735
                    } else {
736
                        // Call hook functions for extra processing
737
                        if ($name) {
738
                            if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['cObjTypeAndClassDefault'])) {
739
                                trigger_error('The hook $TYPO3_CONF_VARS[SC_OPTIONS][tslib/class.tslib_content.php][cObjTypeAndClassDefault] for adding custom processing will be removed. Use a custom cObject which you can register directly instead.', E_USER_DEPRECATED);
740
                            }
741
                            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['cObjTypeAndClassDefault'] ?? [] as $className) {
742
                                $hookObject = GeneralUtility::makeInstance($className);
743
                                if (!$hookObject instanceof ContentObjectGetSingleHookInterface) {
744
                                    throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetSingleHookInterface::class, 1195043731);
745
                                }
746
                                /** @var ContentObjectGetSingleHookInterface $hookObject */
747
                                $content .= $hookObject->getSingleContentObject($name, (array)$conf, $TSkey, $this);
748
                            }
749
                        } else {
750
                            // Log error in AdminPanel
751
                            $warning = sprintf('Content Object "%s" does not exist', $name);
752
                            $timeTracker->setTSlogMessage($warning, 2);
753
                        }
754
                    }
755
                }
756
            }
757
            if ($timeTracker->LR) {
758
                $timeTracker->pull($content);
759
            }
760
        }
761
        // Increasing on exit...
762
        $this->getTypoScriptFrontendController()->cObjectDepthCounter++;
763
        return $content;
764
    }
765
766
    /**
767
     * Returns a new content object of type $name.
768
     * This content object needs to be registered as content object
769
     * in $this->contentObjectClassMap
770
     *
771
     * @param string $name
772
     * @return AbstractContentObject|null
773
     * @throws ContentRenderingException
774
     */
775
    public function getContentObject($name)
776
    {
777
        if (!isset($this->contentObjectClassMap[$name])) {
778
            return null;
779
        }
780
        $fullyQualifiedClassName = $this->contentObjectClassMap[$name];
781
        $contentObject = GeneralUtility::makeInstance($fullyQualifiedClassName, $this);
782
        if (!($contentObject instanceof AbstractContentObject)) {
783
            throw new ContentRenderingException(sprintf('Registered content object class name "%s" must be an instance of AbstractContentObject, but is not!', $fullyQualifiedClassName), 1422564295);
784
        }
785
        return $contentObject;
786
    }
787
788
    /********************************************
789
     *
790
     * Functions rendering content objects (cObjects)
791
     *
792
     ********************************************/
793
794
    /**
795
     * Renders a content object by taking exception and cache handling
796
     * into consideration
797
     *
798
     * @param AbstractContentObject $contentObject Content object instance
799
     * @param array $configuration Array of TypoScript properties
800
     *
801
     * @throws ContentRenderingException
802
     * @throws \Exception
803
     * @return string
804
     */
805
    public function render(AbstractContentObject $contentObject, $configuration = [])
806
    {
807
        $content = '';
808
809
        // Evaluate possible cache and return
810
        $cacheConfiguration = $configuration['cache.'] ?? null;
811
        if ($cacheConfiguration !== null) {
812
            unset($configuration['cache.']);
813
            $cache = $this->getFromCache($cacheConfiguration);
814
            if ($cache !== false) {
815
                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...
816
            }
817
        }
818
819
        // Render content
820
        try {
821
            $content .= $contentObject->render($configuration);
822
        } catch (ContentRenderingException $exception) {
823
            // Content rendering Exceptions indicate a critical problem which should not be
824
            // caught e.g. when something went wrong with Exception handling itself
825
            throw $exception;
826
        } catch (\Exception $exception) {
827
            $exceptionHandler = $this->createExceptionHandler($configuration);
828
            if ($exceptionHandler === null) {
829
                throw $exception;
830
            }
831
            $content = $exceptionHandler->handle($exception, $contentObject, $configuration);
832
        }
833
834
        // Store cache
835
        if ($cacheConfiguration !== null && !$this->getTypoScriptFrontendController()->no_cache) {
836
            $key = $this->calculateCacheKey($cacheConfiguration);
837
            if (!empty($key)) {
838
                /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cacheFrontend */
839
                $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)->getCache('hash');
840
                $tags = $this->calculateCacheTags($cacheConfiguration);
841
                $lifetime = $this->calculateCacheLifetime($cacheConfiguration);
842
                $cacheFrontend->set($key, $content, $tags, $lifetime);
843
            }
844
        }
845
846
        return $content;
847
    }
848
849
    /**
850
     * Creates the content object exception handler from local content object configuration
851
     * or, from global configuration if not explicitly disabled in local configuration
852
     *
853
     * @param array $configuration
854
     * @return ExceptionHandlerInterface|null
855
     * @throws ContentRenderingException
856
     */
857
    protected function createExceptionHandler($configuration = [])
858
    {
859
        $exceptionHandler = null;
860
        $exceptionHandlerClassName = $this->determineExceptionHandlerClassName($configuration);
861
        if (!empty($exceptionHandlerClassName)) {
862
            $exceptionHandler = GeneralUtility::makeInstance($exceptionHandlerClassName, $this->mergeExceptionHandlerConfiguration($configuration));
863
            if (!$exceptionHandler instanceof ExceptionHandlerInterface) {
864
                throw new ContentRenderingException('An exception handler was configured but the class does not exist or does not implement the ExceptionHandlerInterface', 1403653369);
865
            }
866
        }
867
868
        return $exceptionHandler;
869
    }
870
871
    /**
872
     * Determine exception handler class name from global and content object configuration
873
     *
874
     * @param array $configuration
875
     * @return string|null
876
     */
877
    protected function determineExceptionHandlerClassName($configuration)
878
    {
879
        $exceptionHandlerClassName = null;
880
        $tsfe = $this->getTypoScriptFrontendController();
881
        if (!isset($tsfe->config['config']['contentObjectExceptionHandler'])) {
882
            if (Environment::getContext()->isProduction()) {
883
                $exceptionHandlerClassName = '1';
884
            }
885
        } else {
886
            $exceptionHandlerClassName = $tsfe->config['config']['contentObjectExceptionHandler'];
887
        }
888
889
        if (isset($configuration['exceptionHandler'])) {
890
            $exceptionHandlerClassName = $configuration['exceptionHandler'];
891
        }
892
893
        if ($exceptionHandlerClassName === '1') {
894
            $exceptionHandlerClassName = ProductionExceptionHandler::class;
895
        }
896
897
        return $exceptionHandlerClassName;
898
    }
899
900
    /**
901
     * Merges global exception handler configuration with the one from the content object
902
     * and returns the merged exception handler configuration
903
     *
904
     * @param array $configuration
905
     * @return array
906
     */
907
    protected function mergeExceptionHandlerConfiguration($configuration)
908
    {
909
        $exceptionHandlerConfiguration = [];
910
        $tsfe = $this->getTypoScriptFrontendController();
911
        if (!empty($tsfe->config['config']['contentObjectExceptionHandler.'])) {
912
            $exceptionHandlerConfiguration = $tsfe->config['config']['contentObjectExceptionHandler.'];
913
        }
914
        if (!empty($configuration['exceptionHandler.'])) {
915
            $exceptionHandlerConfiguration = array_replace_recursive($exceptionHandlerConfiguration, $configuration['exceptionHandler.']);
916
        }
917
918
        return $exceptionHandlerConfiguration;
919
    }
920
921
    /**
922
     * Retrieves a type of object called as USER or USER_INT. Object can detect their
923
     * type by using this call. It returns OBJECTTYPE_USER_INT or OBJECTTYPE_USER depending on the
924
     * current object execution. In all other cases it will return FALSE to indicate
925
     * a call out of context.
926
     *
927
     * @return mixed One of OBJECTTYPE_ class constants or FALSE
928
     */
929
    public function getUserObjectType()
930
    {
931
        return $this->userObjectType;
932
    }
933
934
    /**
935
     * Sets the user object type
936
     *
937
     * @param mixed $userObjectType
938
     */
939
    public function setUserObjectType($userObjectType)
940
    {
941
        $this->userObjectType = $userObjectType;
942
    }
943
944
    /**
945
     * Requests the current USER object to be converted to USER_INT.
946
     */
947
    public function convertToUserIntObject()
948
    {
949
        if ($this->userObjectType !== self::OBJECTTYPE_USER) {
0 ignored issues
show
introduced by
The condition $this->userObjectType !== self::OBJECTTYPE_USER is always true.
Loading history...
950
            $this->getTimeTracker()->setTSlogMessage(self::class . '::convertToUserIntObject() is called in the wrong context or for the wrong object type', 2);
951
        } else {
952
            $this->doConvertToUserIntObject = true;
953
        }
954
    }
955
956
    /************************************
957
     *
958
     * Various helper functions for content objects:
959
     *
960
     ************************************/
961
    /**
962
     * Converts a given config in Flexform to a conf-array
963
     *
964
     * @param string|array $flexData Flexform data
965
     * @param array $conf Array to write the data into, by reference
966
     * @param bool $recursive Is set if called recursive. Don't call function with this parameter, it's used inside the function only
967
     */
968
    public function readFlexformIntoConf($flexData, &$conf, $recursive = false)
969
    {
970
        if ($recursive === false && is_string($flexData)) {
971
            $flexData = GeneralUtility::xml2array($flexData, 'T3');
972
        }
973
        if (is_array($flexData) && isset($flexData['data']['sDEF']['lDEF'])) {
974
            $flexData = $flexData['data']['sDEF']['lDEF'];
975
        }
976
        if (!is_array($flexData)) {
977
            return;
978
        }
979
        foreach ($flexData as $key => $value) {
980
            if (!is_array($value)) {
981
                continue;
982
            }
983
            if (isset($value['el'])) {
984
                if (is_array($value['el']) && !empty($value['el'])) {
985
                    foreach ($value['el'] as $ekey => $element) {
986
                        if (isset($element['vDEF'])) {
987
                            $conf[$ekey] = $element['vDEF'];
988
                        } else {
989
                            if (is_array($element)) {
990
                                $this->readFlexformIntoConf($element, $conf[$key][key($element)][$ekey], true);
991
                            } else {
992
                                $this->readFlexformIntoConf($element, $conf[$key][$ekey], true);
993
                            }
994
                        }
995
                    }
996
                } else {
997
                    $this->readFlexformIntoConf($value['el'], $conf[$key], true);
998
                }
999
            }
1000
            if (isset($value['vDEF'])) {
1001
                $conf[$key] = $value['vDEF'];
1002
            }
1003
        }
1004
    }
1005
1006
    /**
1007
     * Returns all parents of the given PID (Page UID) list
1008
     *
1009
     * @param string $pidList A list of page Content-Element PIDs (Page UIDs) / stdWrap
1010
     * @param array $pidConf stdWrap array for the list
1011
     * @return string A list of PIDs
1012
     * @internal
1013
     */
1014
    public function getSlidePids($pidList, $pidConf)
1015
    {
1016
        // todo: phpstan states that $pidConf always exists and is not nullable. At the moment, this is a false positive
1017
        //       as null can be passed into this method via $pidConf. As soon as more strict types are used, this isset
1018
        //       check must be replaced with a more appropriate check like empty or count.
1019
        $pidList = isset($pidConf) ? trim($this->stdWrap($pidList, $pidConf)) : trim($pidList);
1020
        if ($pidList === '') {
1021
            $pidList = 'this';
1022
        }
1023
        $tsfe = $this->getTypoScriptFrontendController();
1024
        $listArr = null;
1025
        if (trim($pidList)) {
1026
            $listArr = GeneralUtility::intExplode(',', str_replace('this', $tsfe->contentPid, $pidList));
1027
            $listArr = $this->checkPidArray($listArr);
0 ignored issues
show
Bug introduced by
$listArr of type string[] is incompatible with the type integer[] expected by parameter $pageIds of TYPO3\CMS\Frontend\Conte...nderer::checkPidArray(). ( Ignorable by Annotation )

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

1027
            $listArr = $this->checkPidArray(/** @scrutinizer ignore-type */ $listArr);
Loading history...
1028
        }
1029
        $pidList = [];
1030
        if (is_array($listArr) && !empty($listArr)) {
1031
            foreach ($listArr as $uid) {
1032
                $page = $tsfe->sys_page->getPage($uid);
1033
                if (!$page['is_siteroot']) {
1034
                    $pidList[] = $page['pid'];
1035
                }
1036
            }
1037
        }
1038
        return implode(',', $pidList);
1039
    }
1040
1041
    /**
1042
     * Returns a <img> tag with the image file defined by $file and processed according to the properties in the TypoScript array.
1043
     * Mostly this function is a sub-function to the IMAGE function which renders the IMAGE cObject in TypoScript.
1044
     * This function is called by "$this->cImage($conf['file'], $conf);" from IMAGE().
1045
     *
1046
     * @param string $file File TypoScript resource
1047
     * @param array $conf TypoScript configuration properties
1048
     * @return string HTML <img> tag, (possibly wrapped in links and other HTML) if any image found.
1049
     * @deprecated will be removed in TYPO3 v11.0
1050
     * @see IMAGE()
1051
     */
1052
    public function cImage($file, $conf)
1053
    {
1054
        trigger_error('cObj->cImage() will be removed in TYPO3 v11.0. This functionality is integrated into ImageContentObject now.', E_USER_DEPRECATED);
1055
        $tsfe = $this->getTypoScriptFrontendController();
1056
        $info = $this->getImgResource($file, $conf['file.']);
1057
        $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...
1058
        if (!is_array($info)) {
1059
            return '';
1060
        }
1061
        if (is_file(Environment::getPublicPath() . '/' . $info['3'])) {
1062
            $source = $tsfe->absRefPrefix . str_replace('%2F', '/', rawurlencode($info['3']));
1063
        } else {
1064
            $source = $info[3];
1065
        }
1066
        // Remove file objects for AssetCollector, as it only allows to store scalar values
1067
        unset($info['originalFile'], $info['processedFile']);
1068
        GeneralUtility::makeInstance(AssetCollector::class)->addMedia(
1069
            $source,
1070
            $info
1071
        );
1072
1073
        $layoutKey = $this->stdWrap($conf['layoutKey'], $conf['layoutKey.']);
1074
        $imageTagTemplate = $this->getImageTagTemplate($layoutKey, $conf);
1075
        $sourceCollection = $this->getImageSourceCollection($layoutKey, $conf, $file);
1076
1077
        // This array is used to collect the image-refs on the page...
1078
        $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...
1079
        $altParam = $this->getAltParam($conf);
1080
        $params = $this->stdWrapValue('params', $conf);
1081
        if ($params !== '' && $params[0] !== ' ') {
1082
            $params = ' ' . $params;
1083
        }
1084
1085
        $imageTagValues = [
1086
            'width' =>  (int)$info[0],
1087
            'height' => (int)$info[1],
1088
            'src' => htmlspecialchars($source),
1089
            'params' => $params,
1090
            'altParams' => $altParam,
1091
            'border' =>  $this->getBorderAttr(' border="' . (int)$conf['border'] . '"'),
1092
            'sourceCollection' => $sourceCollection,
1093
            'selfClosingTagSlash' => !empty($tsfe->xhtmlDoctype) ? ' /' : '',
1094
        ];
1095
1096
        $markerTemplateEngine = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
1097
        $theValue = $markerTemplateEngine->substituteMarkerArray($imageTagTemplate, $imageTagValues, '###|###', true, true);
1098
1099
        $linkWrap = isset($conf['linkWrap.']) ? $this->stdWrap($conf['linkWrap'], $conf['linkWrap.']) : $conf['linkWrap'];
1100
        if ($linkWrap) {
1101
            $theValue = $this->linkWrap($theValue, $linkWrap);
1102
        } elseif ($conf['imageLinkWrap']) {
1103
            $originalFile = !empty($info['originalFile']) ? $info['originalFile'] : $info['origFile'];
1104
            $theValue = $this->imageLinkWrap($theValue, $originalFile, $conf['imageLinkWrap.']);
1105
        }
1106
        $wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
1107
        if ((string)$wrap !== '') {
1108
            $theValue = $this->wrap($theValue, $conf['wrap']);
1109
        }
1110
        return $theValue;
1111
    }
1112
1113
    /**
1114
     * Returns the 'border' attribute for an <img> tag only if the doctype is not xhtml_strict, xhtml_11 or html5
1115
     * or if the config parameter 'disableImgBorderAttr' is not set.
1116
     *
1117
     * @param string $borderAttr The border attribute
1118
     * @return string The border attribute
1119
     * @deprecated will be removed in TYPO3 v11.0.
1120
     */
1121
    public function getBorderAttr($borderAttr)
1122
    {
1123
        trigger_error('cObj->getBorderAttr() will be removed in TYPO3 v11.0. This functionality is integrated into ImageContentObject.', E_USER_DEPRECATED);
1124
        $tsfe = $this->getTypoScriptFrontendController();
1125
        $docType = $tsfe->xhtmlDoctype;
1126
        if (
1127
            $docType !== 'xhtml_strict' && $docType !== 'xhtml_11'
1128
            && $tsfe->config['config']['doctype'] !== 'html5'
1129
            && !$tsfe->config['config']['disableImgBorderAttr']
1130
        ) {
1131
            return $borderAttr;
1132
        }
1133
        return '';
1134
    }
1135
1136
    /**
1137
     * Returns the html-template for rendering the image-Tag if no template is defined via typoscript the
1138
     * default <img> tag template is returned
1139
     *
1140
     * @param string $layoutKey rendering key
1141
     * @param array $conf TypoScript configuration properties
1142
     * @return string
1143
     * @deprecated will be removed in TYPO3 v11.0.
1144
     */
1145
    public function getImageTagTemplate($layoutKey, $conf)
1146
    {
1147
        trigger_error('cObj->getImageTagTemplate() will be removed in TYPO3 v11.0. This functionality is integrated into ImageContentObject.', E_USER_DEPRECATED);
1148
        if ($layoutKey && isset($conf['layout.']) && isset($conf['layout.'][$layoutKey . '.'])) {
1149
            $imageTagLayout = $this->stdWrap(
1150
                $conf['layout.'][$layoutKey . '.']['element'] ?? '',
1151
                $conf['layout.'][$layoutKey . '.']['element.'] ?? []
1152
            );
1153
        } else {
1154
            $imageTagLayout = '<img src="###SRC###" width="###WIDTH###" height="###HEIGHT###" ###PARAMS### ###ALTPARAMS### ###BORDER######SELFCLOSINGTAGSLASH###>';
1155
        }
1156
        return $imageTagLayout;
1157
    }
1158
1159
    /**
1160
     * Render alternate sources for the image tag. If no source collection is given an empty string is returned.
1161
     *
1162
     * @param string $layoutKey rendering key
1163
     * @param array $conf TypoScript configuration properties
1164
     * @param string $file
1165
     * @throws \UnexpectedValueException
1166
     * @return string
1167
     * @deprecated will be removed in TYPO3 v11.0.
1168
     */
1169
    public function getImageSourceCollection($layoutKey, $conf, $file)
1170
    {
1171
        trigger_error('cObj->getImageSourceCollection() will be removed in TYPO3 v11.0. This functionality is integrated into ImageContentObject.', E_USER_DEPRECATED);
1172
        $sourceCollection = '';
1173
        if ($layoutKey
1174
            && isset($conf['sourceCollection.']) && $conf['sourceCollection.']
1175
            && (
1176
                isset($conf['layout.'][$layoutKey . '.']['source']) && $conf['layout.'][$layoutKey . '.']['source']
1177
                || isset($conf['layout.'][$layoutKey . '.']['source.']) && $conf['layout.'][$layoutKey . '.']['source.']
1178
            )
1179
        ) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
1180
1181
            // find active sourceCollection
1182
            $activeSourceCollections = [];
1183
            foreach ($conf['sourceCollection.'] as $sourceCollectionKey => $sourceCollectionConfiguration) {
1184
                if (substr($sourceCollectionKey, -1) === '.') {
1185
                    if (empty($sourceCollectionConfiguration['if.']) || $this->checkIf($sourceCollectionConfiguration['if.'])) {
1186
                        $activeSourceCollections[] = $sourceCollectionConfiguration;
1187
                    }
1188
                }
1189
            }
1190
1191
            // apply option split to configurations
1192
            $tsfe = $this->getTypoScriptFrontendController();
1193
            $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
1194
            $srcLayoutOptionSplitted = $typoScriptService->explodeConfigurationForOptionSplit((array)$conf['layout.'][$layoutKey . '.'], count($activeSourceCollections));
1195
1196
            // render sources
1197
            foreach ($activeSourceCollections as $key => $sourceConfiguration) {
1198
                $sourceLayout = $this->stdWrap(
1199
                    $srcLayoutOptionSplitted[$key]['source'] ?? '',
1200
                    $srcLayoutOptionSplitted[$key]['source.'] ?? []
1201
                );
1202
1203
                $sourceRenderConfiguration = [
1204
                    'file' => $file,
1205
                    'file.' => $conf['file.'] ?? null
1206
                ];
1207
1208
                if (isset($sourceConfiguration['quality']) || isset($sourceConfiguration['quality.'])) {
1209
                    $imageQuality = $sourceConfiguration['quality'] ?? '';
1210
                    if (isset($sourceConfiguration['quality.'])) {
1211
                        $imageQuality = $this->stdWrap($sourceConfiguration['quality'], $sourceConfiguration['quality.']);
1212
                    }
1213
                    if ($imageQuality) {
1214
                        $sourceRenderConfiguration['file.']['params'] = '-quality ' . (int)$imageQuality;
1215
                    }
1216
                }
1217
1218
                if (isset($sourceConfiguration['pixelDensity'])) {
1219
                    $pixelDensity = (int)$this->stdWrap(
1220
                        $sourceConfiguration['pixelDensity'] ?? '',
1221
                        $sourceConfiguration['pixelDensity.'] ?? []
1222
                    );
1223
                } else {
1224
                    $pixelDensity = 1;
1225
                }
1226
                $dimensionKeys = ['width', 'height', 'maxW', 'minW', 'maxH', 'minH', 'maxWidth', 'maxHeight', 'XY'];
1227
                foreach ($dimensionKeys as $dimensionKey) {
1228
                    $dimension = $this->stdWrap(
1229
                        $sourceConfiguration[$dimensionKey] ?? '',
1230
                        $sourceConfiguration[$dimensionKey . '.'] ?? []
1231
                    );
1232
                    if (!$dimension) {
1233
                        $dimension = $this->stdWrap(
1234
                            $conf['file.'][$dimensionKey] ?? '',
1235
                            $conf['file.'][$dimensionKey . '.'] ?? []
1236
                        );
1237
                    }
1238
                    if ($dimension) {
1239
                        if (strpos($dimension, 'c') !== false && ($dimensionKey === 'width' || $dimensionKey === 'height')) {
1240
                            $dimensionParts = explode('c', $dimension, 2);
1241
                            $dimension = ((int)$dimensionParts[0] * $pixelDensity) . 'c';
1242
                            if ($dimensionParts[1]) {
1243
                                $dimension .= $dimensionParts[1];
1244
                            }
1245
                        } elseif ($dimensionKey === 'XY') {
1246
                            $dimensionParts = GeneralUtility::intExplode(',', $dimension, false, 2);
1247
                            $dimension = $dimensionParts[0] * $pixelDensity;
1248
                            if ($dimensionParts[1]) {
1249
                                $dimension .= ',' . $dimensionParts[1] * $pixelDensity;
1250
                            }
1251
                        } else {
1252
                            $dimension = (int)$dimension * $pixelDensity;
1253
                        }
1254
                        $sourceRenderConfiguration['file.'][$dimensionKey] = $dimension;
1255
                        // Remove the stdWrap properties for dimension as they have been processed already above.
1256
                        unset($sourceRenderConfiguration['file.'][$dimensionKey . '.']);
1257
                    }
1258
                }
1259
                $sourceInfo = $this->getImgResource($sourceRenderConfiguration['file'], $sourceRenderConfiguration['file.']);
1260
                if ($sourceInfo) {
1261
                    $sourceConfiguration['width'] = $sourceInfo[0];
1262
                    $sourceConfiguration['height'] = $sourceInfo[1];
1263
                    $urlPrefix = '';
1264
                    if (parse_url($sourceInfo[3], PHP_URL_HOST) === null) {
1265
                        $urlPrefix = $tsfe->absRefPrefix;
1266
                    }
1267
                    $sourceConfiguration['src'] = htmlspecialchars($urlPrefix . $sourceInfo[3]);
1268
                    $sourceConfiguration['selfClosingTagSlash'] = !empty($tsfe->xhtmlDoctype) ? ' /' : '';
1269
1270
                    $markerTemplateEngine = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
1271
                    $oneSourceCollection = $markerTemplateEngine->substituteMarkerArray($sourceLayout, $sourceConfiguration, '###|###', true, true);
1272
1273
                    foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImageSourceCollection'] ?? [] as $className) {
1274
                        $hookObject = GeneralUtility::makeInstance($className);
1275
                        if (!$hookObject instanceof ContentObjectOneSourceCollectionHookInterface) {
1276
                            throw new \UnexpectedValueException(
1277
                                '$hookObject must implement interface ' . ContentObjectOneSourceCollectionHookInterface::class,
1278
                                1380007853
1279
                            );
1280
                        }
1281
                        $oneSourceCollection = $hookObject->getOneSourceCollection((array)$sourceRenderConfiguration, (array)$sourceConfiguration, $oneSourceCollection, $this);
1282
                    }
1283
1284
                    $sourceCollection .= $oneSourceCollection;
1285
                }
1286
            }
1287
        }
1288
        return $sourceCollection;
1289
    }
1290
1291
    /**
1292
     * Wraps the input string in link-tags that opens the image in a new window.
1293
     *
1294
     * @param string $string String to wrap, probably an <img> tag
1295
     * @param string|File|FileReference $imageFile The original image file
1296
     * @param array $conf TypoScript properties for the "imageLinkWrap" function
1297
     * @return string The input string, $string, wrapped as configured.
1298
     * @see cImage()
1299
     * @internal This method should be used within TYPO3 Core only
1300
     */
1301
    public function imageLinkWrap($string, $imageFile, $conf)
1302
    {
1303
        $string = (string)$string;
1304
        $enable = isset($conf['enable.']) ? $this->stdWrap($conf['enable'], $conf['enable.']) : $conf['enable'];
1305
        if (!$enable) {
1306
            return $string;
1307
        }
1308
        $content = (string)$this->typoLink($string, $conf['typolink.']);
1309
        if (isset($conf['file.'])) {
1310
            $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

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

1378
                        $url = $altUrl . ($conf['JSwindow.']['altUrl_noDefaultParams'] ? '' : '?file=' . rawurlencode(/** @scrutinizer ignore-type */ $imageFile) . $params);
Loading history...
1379
                    }
1380
                }
1381
1382
                $processedFile = $file->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $conf);
1383
                $JSwindowExpand = isset($conf['JSwindow.']['expand.']) ? $this->stdWrap($conf['JSwindow.']['expand'], $conf['JSwindow.']['expand.']) : $conf['JSwindow.']['expand'];
1384
                $offset = GeneralUtility::intExplode(',', $JSwindowExpand . ',');
1385
                $newWindow = isset($conf['JSwindow.']['newWindow.']) ? $this->stdWrap($conf['JSwindow.']['newWindow'], $conf['JSwindow.']['newWindow.']) : $conf['JSwindow.']['newWindow'];
1386
                $params = [
1387
                    'width' => ($processedFile->getProperty('width') + $offset[0]),
1388
                    'height' => ($processedFile->getProperty('height') + $offset[1]),
1389
                    'status' => '0',
1390
                    'menubar' => '0'
1391
                ];
1392
                // params override existing parameters from above, or add more
1393
                $windowParams = isset($conf['JSwindow.']['params.']) ? $this->stdWrap($conf['JSwindow.']['params'], $conf['JSwindow.']['params.']) : $conf['JSwindow.']['params'];
1394
                $windowParams = explode(',', $windowParams);
1395
                foreach ($windowParams as $windowParam) {
1396
                    [$paramKey, $paramValue] = explode('=', $windowParam);
1397
                    if ($paramValue !== '') {
1398
                        $params[$paramKey] = $paramValue;
1399
                    } else {
1400
                        unset($params[$paramKey]);
1401
                    }
1402
                }
1403
                $paramString = '';
1404
                foreach ($params as $paramKey => $paramValue) {
1405
                    $paramString .= htmlspecialchars($paramKey) . '=' . htmlspecialchars($paramValue) . ',';
1406
                }
1407
1408
                $onClick = 'openPic('
1409
                    . 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

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

1410
                    . '\'' . ($newWindow ? md5(/** @scrutinizer ignore-type */ $url) : 'thePicture') . '\','
Loading history...
1411
                    . GeneralUtility::quoteJSvalue(rtrim($paramString, ',')) . '); return false;';
1412
                $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

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

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

2310
        return $this->numberFormat(/** @scrutinizer ignore-type */ $content, $conf['numberFormat.'] ?? []);
Loading history...
2311
    }
2312
2313
    /**
2314
     * expandList
2315
     * Will return a formatted number based on configuration given as stdWrap properties
2316
     *
2317
     * @param string $content Input value undergoing processing in this function.
2318
     * @return string The processed input value
2319
     */
2320
    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...
2321
    {
2322
        return GeneralUtility::expandList($content);
2323
    }
2324
2325
    /**
2326
     * date
2327
     * Will return a formatted date based on configuration given according to PHP date/gmdate properties
2328
     * Will return gmdate when the property GMT returns TRUE
2329
     *
2330
     * @param string $content Input value undergoing processing in this function.
2331
     * @param array $conf stdWrap properties for date.
2332
     * @return string The processed input value
2333
     */
2334
    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...
2335
    {
2336
        // Check for zero length string to mimic default case of date/gmdate.
2337
        $content = (string)$content === '' ? $GLOBALS['EXEC_TIME'] : (int)$content;
2338
        $content = !empty($conf['date.']['GMT']) ? gmdate($conf['date'] ?? null, $content) : date($conf['date'] ?? null, $content);
2339
        return $content;
2340
    }
2341
2342
    /**
2343
     * strftime
2344
     * Will return a formatted date based on configuration given according to PHP strftime/gmstrftime properties
2345
     * Will return gmstrftime when the property GMT returns TRUE
2346
     *
2347
     * @param string $content Input value undergoing processing in this function.
2348
     * @param array $conf stdWrap properties for strftime.
2349
     * @return string The processed input value
2350
     */
2351
    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...
2352
    {
2353
        // Check for zero length string to mimic default case of strtime/gmstrftime
2354
        $content = (string)$content === '' ? $GLOBALS['EXEC_TIME'] : (int)$content;
2355
        $content = (isset($conf['strftime.']['GMT']) && $conf['strftime.']['GMT'])
2356
            ? gmstrftime($conf['strftime'] ?? null, $content)
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
2357
            : strftime($conf['strftime'] ?? null, $content);
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
2358
        if (!empty($conf['strftime.']['charset'])) {
2359
            $output = mb_convert_encoding($content, 'utf-8', trim(strtolower($conf['strftime.']['charset'])));
2360
            return $output ?: $content;
2361
        }
2362
        return $content;
2363
    }
2364
2365
    /**
2366
     * strtotime
2367
     * Will return a timestamp based on configuration given according to PHP strtotime
2368
     *
2369
     * @param string $content Input value undergoing processing in this function.
2370
     * @param array $conf stdWrap properties for strtotime.
2371
     * @return string The processed input value
2372
     */
2373
    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...
2374
    {
2375
        if ($conf['strtotime'] !== '1') {
2376
            $content .= ' ' . $conf['strtotime'];
2377
        }
2378
        return strtotime($content, $GLOBALS['EXEC_TIME']);
2379
    }
2380
2381
    /**
2382
     * age
2383
     * Will return the age of a given timestamp based on configuration given by stdWrap properties
2384
     *
2385
     * @param string $content Input value undergoing processing in this function.
2386
     * @param array $conf stdWrap properties for age.
2387
     * @return string The processed input value
2388
     */
2389
    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...
2390
    {
2391
        return $this->calcAge((int)($GLOBALS['EXEC_TIME'] ?? 0) - (int)$content, $conf['age'] ?? null);
2392
    }
2393
2394
    /**
2395
     * case
2396
     * Will transform the content to be upper or lower case only
2397
     * Leaves HTML tags untouched
2398
     *
2399
     * @param string $content Input value undergoing processing in this function.
2400
     * @param array $conf stdWrap properties for case.
2401
     * @return string The processed input value
2402
     */
2403
    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...
2404
    {
2405
        return $this->HTMLcaseshift($content, $conf['case']);
2406
    }
2407
2408
    /**
2409
     * bytes
2410
     * Will return the size of a given number in Bytes	 *
2411
     *
2412
     * @param string $content Input value undergoing processing in this function.
2413
     * @param array $conf stdWrap properties for bytes.
2414
     * @return string The processed input value
2415
     */
2416
    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...
2417
    {
2418
        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

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

3044
            $char = chr(/** @scrutinizer ignore-type */ $char);
Loading history...
3045
        }
3046
        $temp = explode($char, $content);
3047
        $last = '' . (count($temp) - 1);
3048
        // Take a random item if requested
3049
        if ($listNum === 'rand') {
3050
            $listNum = random_int(0, count($temp) - 1);
3051
        }
3052
        $index = $this->calc(str_ireplace('last', $last, $listNum));
3053
        return $temp[$index];
3054
    }
3055
3056
    /**
3057
     * Compares values together based on the settings in the input TypoScript array and returns the comparison result.
3058
     * Implements the "if" function in TYPO3 TypoScript
3059
     *
3060
     * @param array $conf TypoScript properties defining what to compare
3061
     * @return bool
3062
     * @see stdWrap()
3063
     * @see _parseFunc()
3064
     */
3065
    public function checkIf($conf)
3066
    {
3067
        if (!is_array($conf)) {
0 ignored issues
show
introduced by
The condition is_array($conf) is always true.
Loading history...
3068
            return true;
3069
        }
3070
        if (isset($conf['directReturn'])) {
3071
            return (bool)$conf['directReturn'];
3072
        }
3073
        $flag = true;
3074
        if (isset($conf['isNull.'])) {
3075
            $isNull = $this->stdWrap('', $conf['isNull.']);
3076
            if ($isNull !== null) {
3077
                $flag = false;
3078
            }
3079
        }
3080
        if (isset($conf['isTrue']) || isset($conf['isTrue.'])) {
3081
            $isTrue = isset($conf['isTrue.']) ? trim($this->stdWrap($conf['isTrue'], $conf['isTrue.'])) : trim($conf['isTrue']);
3082
            if (!$isTrue) {
3083
                $flag = false;
3084
            }
3085
        }
3086
        if (isset($conf['isFalse']) || isset($conf['isFalse.'])) {
3087
            $isFalse = isset($conf['isFalse.']) ? trim($this->stdWrap($conf['isFalse'], $conf['isFalse.'])) : trim($conf['isFalse']);
3088
            if ($isFalse) {
3089
                $flag = false;
3090
            }
3091
        }
3092
        if (isset($conf['isPositive']) || isset($conf['isPositive.'])) {
3093
            $number = isset($conf['isPositive.']) ? $this->calc($this->stdWrap($conf['isPositive'], $conf['isPositive.'])) : $this->calc($conf['isPositive']);
3094
            if ($number < 1) {
3095
                $flag = false;
3096
            }
3097
        }
3098
        if ($flag) {
3099
            $value = isset($conf['value.'])
3100
                ? trim($this->stdWrap($conf['value'] ?? '', $conf['value.']))
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
3101
                : trim($conf['value'] ?? '');
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
3102
            if (isset($conf['isGreaterThan']) || isset($conf['isGreaterThan.'])) {
3103
                $number = isset($conf['isGreaterThan.']) ? trim($this->stdWrap($conf['isGreaterThan'], $conf['isGreaterThan.'])) : trim($conf['isGreaterThan']);
3104
                if ($number <= $value) {
3105
                    $flag = false;
3106
                }
3107
            }
3108
            if (isset($conf['isLessThan']) || isset($conf['isLessThan.'])) {
3109
                $number = isset($conf['isLessThan.']) ? trim($this->stdWrap($conf['isLessThan'], $conf['isLessThan.'])) : trim($conf['isLessThan']);
3110
                if ($number >= $value) {
3111
                    $flag = false;
3112
                }
3113
            }
3114
            if (isset($conf['equals']) || isset($conf['equals.'])) {
3115
                $number = isset($conf['equals.']) ? trim($this->stdWrap($conf['equals'], $conf['equals.'])) : trim($conf['equals']);
3116
                if ($number != $value) {
3117
                    $flag = false;
3118
                }
3119
            }
3120
            if (isset($conf['isInList']) || isset($conf['isInList.'])) {
3121
                $number = isset($conf['isInList.']) ? trim($this->stdWrap($conf['isInList'], $conf['isInList.'])) : trim($conf['isInList']);
3122
                if (!GeneralUtility::inList($value, $number)) {
3123
                    $flag = false;
3124
                }
3125
            }
3126
            if (isset($conf['bitAnd']) || isset($conf['bitAnd.'])) {
3127
                $number = isset($conf['bitAnd.']) ? trim($this->stdWrap($conf['bitAnd'], $conf['bitAnd.'])) : trim($conf['bitAnd']);
3128
                if ((new BitSet($number))->get($value) === false) {
0 ignored issues
show
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

3128
                if ((new BitSet(/** @scrutinizer ignore-type */ $number))->get($value) === false) {
Loading history...
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

3128
                if ((new BitSet($number))->get(/** @scrutinizer ignore-type */ $value) === false) {
Loading history...
3129
                    $flag = false;
3130
                }
3131
            }
3132
        }
3133
        if ($conf['negate'] ?? false) {
3134
            $flag = !$flag;
3135
        }
3136
        return $flag;
3137
    }
3138
3139
    /**
3140
     * Passes the input value, $theValue, to an instance of "\TYPO3\CMS\Core\Html\HtmlParser"
3141
     * together with the TypoScript options which are first converted from a TS style array
3142
     * to a set of arrays with options for the \TYPO3\CMS\Core\Html\HtmlParser class.
3143
     *
3144
     * @param string $theValue The value to parse by the class \TYPO3\CMS\Core\Html\HtmlParser
3145
     * @param array $conf TypoScript properties for the parser. See link.
3146
     * @return string Return value.
3147
     * @see stdWrap()
3148
     * @see \TYPO3\CMS\Core\Html\HtmlParser::HTMLparserConfig()
3149
     * @see \TYPO3\CMS\Core\Html\HtmlParser::HTMLcleaner()
3150
     */
3151
    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...
3152
    {
3153
        $htmlParser = GeneralUtility::makeInstance(HtmlParser::class);
3154
        $htmlParserCfg = $htmlParser->HTMLparserConfig($conf);
3155
        return $htmlParser->HTMLcleaner($theValue, $htmlParserCfg[0], $htmlParserCfg[1], $htmlParserCfg[2], $htmlParserCfg[3]);
3156
    }
3157
3158
    /**
3159
     * Wrapping input value in a regular "wrap" but parses the wrapping value first for "insertData" codes.
3160
     *
3161
     * @param string $content Input string being wrapped
3162
     * @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.
3163
     * @return string Output string wrapped in the wrapping value.
3164
     * @see insertData()
3165
     * @see stdWrap()
3166
     */
3167
    public function dataWrap($content, $wrap)
3168
    {
3169
        return $this->wrap($content, $this->insertData($wrap));
3170
    }
3171
3172
    /**
3173
     * Implements the "insertData" property of stdWrap meaning that if strings matching {...} is found in the input string they
3174
     * will be substituted with the return value from getData (datatype) which is passed the content of the curly braces.
3175
     * If the content inside the curly braces starts with a hash sign {#...} it is a field name that must be quoted by Doctrine
3176
     * DBAL and is skipped here for later processing.
3177
     *
3178
     * Example: If input string is "This is the page title: {page:title}" then the part, '{page:title}', will be substituted with
3179
     * the current pages title field value.
3180
     *
3181
     * @param string $str Input value
3182
     * @return string Processed input value
3183
     * @see getData()
3184
     * @see stdWrap()
3185
     * @see dataWrap()
3186
     */
3187
    public function insertData($str)
3188
    {
3189
        $inside = 0;
3190
        $newVal = '';
3191
        $pointer = 0;
3192
        $totalLen = strlen($str);
3193
        do {
3194
            if (!$inside) {
3195
                $len = strcspn(substr($str, $pointer), '{');
3196
                $newVal .= substr($str, $pointer, $len);
3197
                $inside = true;
3198
                if (substr($str, $pointer + $len + 1, 1) === '#') {
3199
                    $len2 = strcspn(substr($str, $pointer + $len), '}');
3200
                    $newVal .= substr($str, $pointer + $len, $len2);
3201
                    $len += $len2;
3202
                    $inside = false;
3203
                }
3204
            } else {
3205
                $len = strcspn(substr($str, $pointer), '}') + 1;
3206
                $newVal .= $this->getData(substr($str, $pointer + 1, $len - 2), $this->data);
3207
                $inside = false;
3208
            }
3209
            $pointer += $len;
3210
        } while ($pointer < $totalLen);
3211
        return $newVal;
3212
    }
3213
3214
    /**
3215
     * 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.
3216
     * Notice; this function (used by stdWrap) can be disabled by a "config.disablePrefixComment" setting in TypoScript.
3217
     *
3218
     * @param string $str Input value
3219
     * @param array $conf TypoScript Configuration (not used at this point.)
3220
     * @param string $content The content to wrap the comment around.
3221
     * @return string Processed input value
3222
     * @see stdWrap()
3223
     */
3224
    public function prefixComment($str, $conf, $content)
3225
    {
3226
        if (empty($str)) {
3227
            return $content;
3228
        }
3229
        $parts = explode('|', $str);
3230
        $indent = (int)$parts[0];
3231
        $comment = htmlspecialchars($this->insertData($parts[1]));
3232
        $output = LF
3233
            . str_pad('', $indent, "\t") . '<!-- ' . $comment . ' [begin] -->' . LF
3234
            . str_pad('', $indent + 1, "\t") . $content . LF
3235
            . str_pad('', $indent, "\t") . '<!-- ' . $comment . ' [end] -->' . LF
3236
            . str_pad('', $indent + 1, "\t");
3237
        return $output;
3238
    }
3239
3240
    /**
3241
     * Implements the stdWrap property "substring" which is basically a TypoScript implementation of the PHP function, substr()
3242
     *
3243
     * @param string $content The string to perform the operation on
3244
     * @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().
3245
     * @return string The processed input value.
3246
     * @internal
3247
     * @see stdWrap()
3248
     */
3249
    public function substring($content, $options)
3250
    {
3251
        $options = GeneralUtility::intExplode(',', $options . ',');
3252
        if ($options[1]) {
3253
            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

3253
            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

3253
            return mb_substr($content, $options[0], /** @scrutinizer ignore-type */ $options[1], 'utf-8');
Loading history...
3254
        }
3255
        return mb_substr($content, $options[0], null, 'utf-8');
3256
    }
3257
3258
    /**
3259
     * 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.
3260
     *
3261
     * @param string $content The string to perform the operation on
3262
     * @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.
3263
     * @return string The processed input value.
3264
     * @internal
3265
     * @see stdWrap()
3266
     */
3267
    public function crop($content, $options)
3268
    {
3269
        $options = explode('|', $options);
3270
        $chars = (int)$options[0];
3271
        $afterstring = trim($options[1] ?? '');
3272
        $crop2space = trim($options[2] ?? '');
3273
        if ($chars) {
3274
            if (mb_strlen($content, 'utf-8') > abs($chars)) {
3275
                $truncatePosition = false;
3276
                if ($chars < 0) {
3277
                    $content = mb_substr($content, $chars, null, 'utf-8');
3278
                    if ($crop2space) {
3279
                        $truncatePosition = strpos($content, ' ');
3280
                    }
3281
                    $content = $truncatePosition ? $afterstring . substr($content, $truncatePosition) : $afterstring . $content;
3282
                } else {
3283
                    $content = mb_substr($content, 0, $chars, 'utf-8');
3284
                    if ($crop2space) {
3285
                        $truncatePosition = strrpos($content, ' ');
3286
                    }
3287
                    $content = $truncatePosition ? substr($content, 0, $truncatePosition) . $afterstring : $content . $afterstring;
3288
                }
3289
            }
3290
        }
3291
        return $content;
3292
    }
3293
3294
    /**
3295
     * Implements the stdWrap property "cropHTML" which is a modified "substr" function allowing to limit a string length
3296
     * to a certain number of chars (from either start or end of string) and having a pre/postfix applied if the string
3297
     * really was cropped.
3298
     *
3299
     * Compared to stdWrap.crop it respects HTML tags and entities.
3300
     *
3301
     * @param string $content The string to perform the operation on
3302
     * @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.
3303
     * @return string The processed input value.
3304
     * @internal
3305
     * @see stdWrap()
3306
     */
3307
    public function cropHTML($content, $options)
3308
    {
3309
        $options = explode('|', $options);
3310
        $chars = (int)$options[0];
3311
        $absChars = abs($chars);
3312
        $replacementForEllipsis = trim($options[1] ?? '');
3313
        $crop2space = trim($options[2] ?? '') === '1';
3314
        // Split $content into an array(even items in the array are outside the tags, odd numbers are tag-blocks).
3315
        $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';
3316
        $tagsRegEx = '
3317
			(
3318
				(?:
3319
					<!--.*?-->					# a comment
3320
					|
3321
					<canvas[^>]*>.*?</canvas>   # a canvas tag
3322
					|
3323
					<script[^>]*>.*?</script>   # a script tag
3324
					|
3325
					<noscript[^>]*>.*?</noscript> # a noscript tag
3326
					|
3327
					<template[^>]*>.*?</template> # a template tag
3328
				)
3329
				|
3330
				</?(?:' . $tags . ')+			# opening tag (\'<tag\') or closing tag (\'</tag\')
3331
				(?:
3332
					(?:
3333
						(?:
3334
							\\s+\\w[\\w-]*		# EITHER spaces, followed by attribute names
3335
							(?:
3336
								\\s*=?\\s*		# equals
3337
								(?>
3338
									".*?"		# attribute values in double-quotes
3339
									|
3340
									\'.*?\'		# attribute values in single-quotes
3341
									|
3342
									[^\'">\\s]+	# plain attribute values
3343
								)
3344
							)?
3345
						)
3346
						|						# OR a single dash (for TYPO3 link tag)
3347
						(?:
3348
							\\s+-
3349
						)
3350
					)+\\s*
3351
					|							# OR only spaces
3352
					\\s*
3353
				)
3354
				/?>								# closing the tag with \'>\' or \'/>\'
3355
			)';
3356
        $splittedContent = preg_split('%' . $tagsRegEx . '%xs', $content, -1, PREG_SPLIT_DELIM_CAPTURE);
3357
        // Reverse array if we are cropping from right.
3358
        if ($chars < 0) {
3359
            $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

3359
            $splittedContent = array_reverse(/** @scrutinizer ignore-type */ $splittedContent);
Loading history...
3360
        }
3361
        // Crop the text (chars of tag-blocks are not counted).
3362
        $strLen = 0;
3363
        // This is the offset of the content item which was cropped.
3364
        $croppedOffset = null;
3365
        $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

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

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

4635
                        $numericKey = $this->getKey(/** @scrutinizer ignore-type */ $key, $tsfe->tmpl->rootLine);
Loading history...
4636
                        $retVal = $this->rootLineValue($numericKey, 'uid');
4637
                        break;
4638
                    case 'levelfield':
4639
                        $keyParts = GeneralUtility::trimExplode(',', $key);
4640
                        $numericKey = $this->getKey($keyParts[0], $tsfe->tmpl->rootLine);
4641
                        $retVal = $this->rootLineValue($numericKey, $keyParts[1], strtolower($keyParts[2] ?? '') === 'slide');
4642
                        break;
4643
                    case 'fullrootline':
4644
                        $keyParts = GeneralUtility::trimExplode(',', $key);
4645
                        $fullKey = (int)$keyParts[0] - count($tsfe->tmpl->rootLine) + count($tsfe->rootLine);
4646
                        if ($fullKey >= 0) {
4647
                            $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

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

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

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

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

5638
        return implode(',', /** @scrutinizer ignore-type */ $listArr);
Loading history...
5639
    }
5640
5641
    /**
5642
     * Changing character case of a string, converting typically used western charset characters as well.
5643
     *
5644
     * @param string $theValue The string to change case for.
5645
     * @param string $case The direction; either "upper" or "lower
5646
     * @return string
5647
     * @see HTMLcaseshift()
5648
     */
5649
    public function caseshift($theValue, $case)
5650
    {
5651
        switch (strtolower($case)) {
5652
            case 'upper':
5653
                $theValue = mb_strtoupper($theValue, 'utf-8');
5654
                break;
5655
            case 'lower':
5656
                $theValue = mb_strtolower($theValue, 'utf-8');
5657
                break;
5658
            case 'capitalize':
5659
                $theValue = mb_convert_case($theValue, MB_CASE_TITLE, 'utf-8');
5660
                break;
5661
            case 'ucfirst':
5662
                $firstChar = mb_substr($theValue, 0, 1, 'utf-8');
5663
                $firstChar = mb_strtoupper($firstChar, 'utf-8');
5664
                $remainder = mb_substr($theValue, 1, null, 'utf-8');
5665
                $theValue = $firstChar . $remainder;
5666
                break;
5667
            case 'lcfirst':
5668
                $firstChar = mb_substr($theValue, 0, 1, 'utf-8');
5669
                $firstChar = mb_strtolower($firstChar, 'utf-8');
5670
                $remainder = mb_substr($theValue, 1, null, 'utf-8');
5671
                $theValue = $firstChar . $remainder;
5672
                break;
5673
            case 'uppercamelcase':
5674
                $theValue = GeneralUtility::underscoredToUpperCamelCase($theValue);
5675
                break;
5676
            case 'lowercamelcase':
5677
                $theValue = GeneralUtility::underscoredToLowerCamelCase($theValue);
5678
                break;
5679
        }
5680
        return $theValue;
5681
    }
5682
5683
    /**
5684
     * Shifts the case of characters outside of HTML tags in the input string
5685
     *
5686
     * @param string $theValue The string to change case for.
5687
     * @param string $case The direction; either "upper" or "lower
5688
     * @return string
5689
     * @see caseshift()
5690
     */
5691
    public function HTMLcaseshift($theValue, $case)
5692
    {
5693
        $inside = 0;
5694
        $newVal = '';
5695
        $pointer = 0;
5696
        $totalLen = strlen($theValue);
5697
        do {
5698
            if (!$inside) {
5699
                $len = strcspn(substr($theValue, $pointer), '<');
5700
                $newVal .= $this->caseshift(substr($theValue, $pointer, $len), $case);
5701
                $inside = 1;
5702
            } else {
5703
                $len = strcspn(substr($theValue, $pointer), '>') + 1;
5704
                $newVal .= substr($theValue, $pointer, $len);
5705
                $inside = 0;
5706
            }
5707
            $pointer += $len;
5708
        } while ($pointer < $totalLen);
5709
        return $newVal;
5710
    }
5711
5712
    /**
5713
     * Returns the 'age' of the tstamp $seconds
5714
     *
5715
     * @param int $seconds Seconds to return age for. Example: "70" => "1 min", "3601" => "1 hrs
5716
     * @param string $labels The labels of the individual units. Defaults to : ' min| hrs| days| yrs'
5717
     * @return string The formatted string
5718
     */
5719
    public function calcAge($seconds, $labels)
5720
    {
5721
        if (MathUtility::canBeInterpretedAsInteger($labels)) {
5722
            $labels = ' min| hrs| days| yrs| min| hour| day| year';
5723
        } else {
5724
            $labels = str_replace('"', '', $labels);
5725
        }
5726
        $labelArr = explode('|', $labels);
5727
        if (count($labelArr) === 4) {
5728
            $labelArr = array_merge($labelArr, $labelArr);
5729
        }
5730
        $absSeconds = abs($seconds);
5731
        $sign = $seconds > 0 ? 1 : -1;
5732
        if ($absSeconds < 3600) {
5733
            $val = round($absSeconds / 60);
5734
            $seconds = $sign * $val . ($val == 1 ? $labelArr[4] : $labelArr[0]);
5735
        } elseif ($absSeconds < 24 * 3600) {
5736
            $val = round($absSeconds / 3600);
5737
            $seconds = $sign * $val . ($val == 1 ? $labelArr[5] : $labelArr[1]);
5738
        } elseif ($absSeconds < 365 * 24 * 3600) {
5739
            $val = round($absSeconds / (24 * 3600));
5740
            $seconds = $sign * $val . ($val == 1 ? $labelArr[6] : $labelArr[2]);
5741
        } else {
5742
            $val = round($absSeconds / (365 * 24 * 3600));
5743
            $seconds = $sign * $val . ($val == 1 ? ($labelArr[7] ?? null) : ($labelArr[3] ?? null));
5744
        }
5745
        return $seconds;
5746
    }
5747
5748
    /**
5749
     * Sends a notification email
5750
     *
5751
     * @param string $message The message content. If blank, no email is sent.
5752
     * @param string $recipients Comma list of recipient email addresses
5753
     * @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.
5754
     * @param string $senderAddress "From" email address
5755
     * @param string $senderName Optional "From" name
5756
     * @param string $replyTo Optional "Reply-To" header email address.
5757
     * @return bool Returns TRUE if sent
5758
     * @deprecated ContentObjectRenderer::sendNotifyEmail is deprecated and will be removed in TYPO3 v11. Consider using the mail API directly
5759
     */
5760
    public function sendNotifyEmail($message, $recipients, $cc, $senderAddress, $senderName = '', $replyTo = '')
5761
    {
5762
        trigger_error('ContentObjectRenderer::sendNotifyEmail is deprecated and will be removed in TYPO3 v11. Consider using the mail API directly.', E_USER_DEPRECATED);
5763
        /** @var MailMessage $mail */
5764
        $mail = GeneralUtility::makeInstance(MailMessage::class);
5765
        $senderName = trim($senderName);
5766
        $senderAddress = trim($senderAddress);
5767
        if ($senderAddress !== '') {
5768
            $mail->from(new Address($senderAddress, $senderName));
5769
        }
5770
        $parsedReplyTo = MailUtility::parseAddresses($replyTo);
5771
        if (!empty($parsedReplyTo)) {
5772
            $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

5772
            $mail->replyTo(/** @scrutinizer ignore-type */ $parsedReplyTo);
Loading history...
5773
        }
5774
        $message = trim($message);
5775
        if ($message !== '') {
5776
            // First line is subject
5777
            $messageParts = explode(LF, $message, 2);
5778
            $subject = trim($messageParts[0]);
5779
            $plainMessage = trim($messageParts[1]);
5780
            $parsedRecipients = MailUtility::parseAddresses($recipients);
5781
            if (!empty($parsedRecipients)) {
5782
                $mail->to(...$parsedRecipients)
5783
                    ->subject($subject)
5784
                    ->text($plainMessage);
5785
                $mail->send();
5786
            }
5787
            $parsedCc = MailUtility::parseAddresses($cc);
5788
            if (!empty($parsedCc)) {
5789
                $from = $mail->getFrom();
5790
                /** @var MailMessage $mail */
5791
                $mail = GeneralUtility::makeInstance(MailMessage::class);
5792
                if (!empty($parsedReplyTo)) {
5793
                    $mail->replyTo($parsedReplyTo);
5794
                }
5795
                $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

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

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

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

6341
                $conf['begin'] = MathUtility::forceIntegerInRange(/** @scrutinizer ignore-type */ ceil($this->calc($conf['begin'])), 0);
Loading history...
6342
                $conf['max'] = MathUtility::forceIntegerInRange(ceil($this->calc($conf['max'])), 0);
6343
                if ($conf['begin'] > 0) {
6344
                    $queryBuilder->setFirstResult($conf['begin']);
6345
                }
6346
                $queryBuilder->setMaxResults($conf['max'] ?: 100000);
6347
            }
6348
        }
6349
6350
        if (!$error) {
6351
            // Setting up tablejoins:
6352
            if ($conf['join']) {
6353
                $joinParts = QueryHelper::parseJoin($conf['join']);
6354
                $queryBuilder->join(
6355
                    $table,
6356
                    $joinParts['tableName'],
6357
                    $joinParts['tableAlias'],
6358
                    $joinParts['joinCondition']
6359
                );
6360
            } elseif ($conf['leftjoin']) {
6361
                $joinParts = QueryHelper::parseJoin($conf['leftjoin']);
6362
                $queryBuilder->leftJoin(
6363
                    $table,
6364
                    $joinParts['tableName'],
6365
                    $joinParts['tableAlias'],
6366
                    $joinParts['joinCondition']
6367
                );
6368
            } elseif ($conf['rightjoin']) {
6369
                $joinParts = QueryHelper::parseJoin($conf['rightjoin']);
6370
                $queryBuilder->rightJoin(
6371
                    $table,
6372
                    $joinParts['tableName'],
6373
                    $joinParts['tableAlias'],
6374
                    $joinParts['joinCondition']
6375
                );
6376
            }
6377
6378
            // Convert the QueryBuilder object into a SQL statement.
6379
            $query = $queryBuilder->getSQL();
6380
6381
            // Replace the markers in the queryParts to handle stdWrap enabled properties
6382
            foreach ($queryMarkers as $marker => $markerValue) {
6383
                // @todo Ugly hack that needs to be cleaned up, with the current architecture
6384
                // @todo for exec_Query / getQuery it's the best we can do.
6385
                $query = str_replace('###' . $marker . '###', $markerValue, $query);
6386
                foreach ($queryParts as $queryPartKey => &$queryPartValue) {
6387
                    $queryPartValue = str_replace('###' . $marker . '###', $markerValue, $queryPartValue);
6388
                }
6389
                unset($queryPartValue);
6390
            }
6391
6392
            return $returnQueryArray ? $this->getQueryArray($queryBuilder) : $query;
6393
        }
6394
6395
        return '';
6396
    }
6397
6398
    /**
6399
     * Helper to transform a QueryBuilder object into a queryParts array that can be used
6400
     * with exec_SELECT_queryArray
6401
     *
6402
     * @param \TYPO3\CMS\Core\Database\Query\QueryBuilder $queryBuilder
6403
     * @return array
6404
     * @throws \RuntimeException
6405
     */
6406
    protected function getQueryArray(QueryBuilder $queryBuilder)
6407
    {
6408
        $fromClauses = [];
6409
        $knownAliases = [];
6410
        $queryParts = [];
6411
6412
        // Loop through all FROM clauses
6413
        foreach ($queryBuilder->getQueryPart('from') as $from) {
6414
            if ($from['alias'] === null) {
6415
                $tableSql = $from['table'];
6416
                $tableReference = $from['table'];
6417
            } else {
6418
                $tableSql = $from['table'] . ' ' . $from['alias'];
6419
                $tableReference = $from['alias'];
6420
            }
6421
6422
            $knownAliases[$tableReference] = true;
6423
6424
            $fromClauses[$tableReference] = $tableSql . $this->getQueryArrayJoinHelper(
6425
                $tableReference,
6426
                $queryBuilder->getQueryPart('join'),
6427
                $knownAliases
6428
            );
6429
        }
6430
6431
        $queryParts['SELECT'] = implode(', ', $queryBuilder->getQueryPart('select'));
6432
        $queryParts['FROM'] = implode(', ', $fromClauses);
6433
        $queryParts['WHERE'] = (string)$queryBuilder->getQueryPart('where') ?: '';
6434
        $queryParts['GROUPBY'] = implode(', ', $queryBuilder->getQueryPart('groupBy'));
6435
        $queryParts['ORDERBY'] = implode(', ', $queryBuilder->getQueryPart('orderBy'));
6436
        if ($queryBuilder->getFirstResult() > 0) {
6437
            $queryParts['LIMIT'] = $queryBuilder->getFirstResult() . ',' . $queryBuilder->getMaxResults();
6438
        } elseif ($queryBuilder->getMaxResults() > 0) {
6439
            $queryParts['LIMIT'] = $queryBuilder->getMaxResults();
6440
        }
6441
6442
        return $queryParts;
6443
    }
6444
6445
    /**
6446
     * Helper to transform the QueryBuilder join part into a SQL fragment.
6447
     *
6448
     * @param string $fromAlias
6449
     * @param array $joinParts
6450
     * @param array $knownAliases
6451
     * @return string
6452
     * @throws \RuntimeException
6453
     */
6454
    protected function getQueryArrayJoinHelper(string $fromAlias, array $joinParts, array &$knownAliases): string
6455
    {
6456
        $sql = '';
6457
6458
        if (isset($joinParts['join'][$fromAlias])) {
6459
            foreach ($joinParts['join'][$fromAlias] as $join) {
6460
                if (array_key_exists($join['joinAlias'], $knownAliases)) {
6461
                    throw new \RuntimeException(
6462
                        'Non unique join alias: "' . $join['joinAlias'] . '" found.',
6463
                        1472748872
6464
                    );
6465
                }
6466
                $sql .= ' ' . strtoupper($join['joinType'])
6467
                    . ' JOIN ' . $join['joinTable'] . ' ' . $join['joinAlias']
6468
                    . ' ON ' . ((string)$join['joinCondition']);
6469
                $knownAliases[$join['joinAlias']] = true;
6470
            }
6471
6472
            foreach ($joinParts['join'][$fromAlias] as $join) {
6473
                $sql .= $this->getQueryArrayJoinHelper($join['joinAlias'], $joinParts, $knownAliases);
6474
            }
6475
        }
6476
6477
        return $sql;
6478
    }
6479
    /**
6480
     * Helper function for getQuery(), creating the WHERE clause of the SELECT query
6481
     *
6482
     * @param string $table The table name
6483
     * @param array $conf The TypoScript configuration properties
6484
     * @return array Associative array containing the prepared data for WHERE, ORDER BY and GROUP BY fragments
6485
     * @throws \InvalidArgumentException
6486
     * @see getQuery()
6487
     */
6488
    protected function getQueryConstraints(string $table, array $conf): array
6489
    {
6490
        // Init:
6491
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
6492
        $expressionBuilder = $queryBuilder->expr();
6493
        $tsfe = $this->getTypoScriptFrontendController();
6494
        $constraints = [];
6495
        $pid_uid_flag = 0;
6496
        $enableFieldsIgnore = [];
6497
        $queryParts = [
6498
            'where' => null,
6499
            'groupBy' => null,
6500
            'orderBy' => null,
6501
        ];
6502
6503
        $isInWorkspace = GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('workspace', 'isOffline');
6504
        $considerMovePlaceholders = (
6505
            $isInWorkspace && $table !== 'pages'
6506
            && !empty($GLOBALS['TCA'][$table]['ctrl']['versioningWS'])
6507
        );
6508
6509
        if (trim($conf['uidInList'])) {
6510
            $listArr = GeneralUtility::intExplode(',', str_replace('this', $tsfe->contentPid, $conf['uidInList']));
6511
6512
            // If move placeholder shall be considered, select via t3ver_move_id
6513
            if ($considerMovePlaceholders) {
6514
                $constraints[] = (string)$expressionBuilder->orX(
6515
                    $expressionBuilder->in($table . '.uid', $listArr),
6516
                    $expressionBuilder->andX(
6517
                        $expressionBuilder->eq(
6518
                            $table . '.t3ver_state',
6519
                            (int)(string)VersionState::cast(VersionState::MOVE_PLACEHOLDER)
6520
                        ),
6521
                        $expressionBuilder->in($table . '.t3ver_move_id', $listArr)
6522
                    )
6523
                );
6524
            } else {
6525
                $constraints[] = (string)$expressionBuilder->in($table . '.uid', $listArr);
6526
            }
6527
            $pid_uid_flag++;
6528
        }
6529
6530
        // Static_* tables are allowed to be fetched from root page
6531
        if (strpos($table, 'static_') === 0) {
6532
            $pid_uid_flag++;
6533
        }
6534
6535
        if (trim($conf['pidInList'])) {
6536
            $listArr = GeneralUtility::intExplode(',', str_replace('this', $tsfe->contentPid, $conf['pidInList']));
6537
            // Removes all pages which are not visible for the user!
6538
            $listArr = $this->checkPidArray($listArr);
0 ignored issues
show
Bug introduced by
$listArr of type string[] is incompatible with the type integer[] expected by parameter $pageIds of TYPO3\CMS\Frontend\Conte...nderer::checkPidArray(). ( Ignorable by Annotation )

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

6538
            $listArr = $this->checkPidArray(/** @scrutinizer ignore-type */ $listArr);
Loading history...
6539
            if (GeneralUtility::inList($conf['pidInList'], 'root')) {
6540
                $listArr[] = 0;
6541
            }
6542
            if (GeneralUtility::inList($conf['pidInList'], '-1')) {
6543
                $listArr[] = -1;
6544
                $enableFieldsIgnore['pid'] = true;
6545
            }
6546
            if (!empty($listArr)) {
6547
                $constraints[] = $expressionBuilder->in($table . '.pid', array_map('intval', $listArr));
6548
                $pid_uid_flag++;
6549
            } else {
6550
                // If not uid and not pid then uid is set to 0 - which results in nothing!!
6551
                $pid_uid_flag = 0;
6552
            }
6553
        }
6554
6555
        // If not uid and not pid then uid is set to 0 - which results in nothing!!
6556
        if (!$pid_uid_flag) {
6557
            $constraints[] = $expressionBuilder->eq($table . '.uid', 0);
6558
        }
6559
6560
        $where = isset($conf['where.']) ? trim($this->stdWrap($conf['where'], $conf['where.'])) : trim($conf['where']);
6561
        if ($where) {
6562
            $constraints[] = QueryHelper::stripLogicalOperatorPrefix($where);
6563
        }
6564
6565
        // Check if the default language should be fetched (= doing overlays), or if only the records of a language should be fetched
6566
        // but only do this for TCA tables that have languages enabled
6567
        $languageConstraint = $this->getLanguageRestriction($expressionBuilder, $table, $conf, GeneralUtility::makeInstance(Context::class));
6568
        if ($languageConstraint !== null) {
6569
            $constraints[] = $languageConstraint;
6570
        }
6571
6572
        // Enablefields
6573
        if ($table === 'pages') {
6574
            $constraints[] = QueryHelper::stripLogicalOperatorPrefix($tsfe->sys_page->where_hid_del);
6575
            $constraints[] = QueryHelper::stripLogicalOperatorPrefix($tsfe->sys_page->where_groupAccess);
6576
        } else {
6577
            $constraints[] = QueryHelper::stripLogicalOperatorPrefix($tsfe->sys_page->enableFields($table, -1, $enableFieldsIgnore));
6578
        }
6579
6580
        // MAKE WHERE:
6581
        if (count($constraints) !== 0) {
6582
            $queryParts['where'] = $expressionBuilder->andX(...$constraints);
6583
        }
6584
        // GROUP BY
6585
        if (trim($conf['groupBy'])) {
6586
            $groupBy = isset($conf['groupBy.'])
6587
                ? trim($this->stdWrap($conf['groupBy'], $conf['groupBy.']))
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
6588
                : trim($conf['groupBy']);
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
6589
            $queryParts['groupBy'] = QueryHelper::parseGroupBy($groupBy);
6590
        }
6591
6592
        // ORDER BY
6593
        if (trim($conf['orderBy'])) {
6594
            $orderByString = isset($conf['orderBy.'])
6595
                ? trim($this->stdWrap($conf['orderBy'], $conf['orderBy.']))
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
6596
                : trim($conf['orderBy']);
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
6597
6598
            $queryParts['orderBy'] = QueryHelper::parseOrderBy($orderByString);
6599
        }
6600
6601
        // Return result:
6602
        return $queryParts;
6603
    }
6604
6605
    /**
6606
     * Adds parts to the WHERE clause that are related to language.
6607
     * This only works on TCA tables which have the [ctrl][languageField] field set or if they
6608
     * have select.languageField = my_language_field set explicitly.
6609
     *
6610
     * It is also possible to disable the language restriction for a query by using select.languageField = 0,
6611
     * if select.languageField is not explicitly set, the TCA default values are taken.
6612
     *
6613
     * If the table is "localizeable" (= any of the criteria above is met), then the DB query is restricted:
6614
     *
6615
     * If the current language aspect has overlays enabled, then the only records with language "0" or "-1" are
6616
     * fetched (the overlays are taken care of later-on).
6617
     * if the current language has overlays but also records without localization-parent (free mode) available,
6618
     * then these are fetched as well. This can explicitly set via select.includeRecordsWithoutDefaultTranslation = 1
6619
     * which overrules the overlayType within the language aspect.
6620
     *
6621
     * If the language aspect has NO overlays enabled, it behaves as in "free mode" (= only fetch the records
6622
     * for the current language.
6623
     *
6624
     * @param ExpressionBuilder $expressionBuilder
6625
     * @param string $table
6626
     * @param array $conf
6627
     * @param Context $context
6628
     * @return string|\TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression|null
6629
     * @throws \TYPO3\CMS\Core\Context\Exception\AspectNotFoundException
6630
     */
6631
    protected function getLanguageRestriction(ExpressionBuilder $expressionBuilder, string $table, array $conf, Context $context)
6632
    {
6633
        $languageField = '';
6634
        $localizationParentField = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] ?? null;
6635
        // Check if the table is translatable, and set the language field by default from the TCA information
6636
        if (!empty($conf['languageField']) || !isset($conf['languageField'])) {
6637
            if (isset($conf['languageField']) && !empty($GLOBALS['TCA'][$table]['columns'][$conf['languageField']])) {
6638
                $languageField = $conf['languageField'];
6639
            } elseif (!empty($GLOBALS['TCA'][$table]['ctrl']['languageField']) && !empty($localizationParentField)) {
6640
                $languageField = $table . '.' . $GLOBALS['TCA'][$table]['ctrl']['languageField'];
6641
            }
6642
        }
6643
6644
        // No language restriction enabled explicitly or available via TCA
6645
        if (empty($languageField)) {
6646
            return null;
6647
        }
6648
6649
        /** @var LanguageAspect $languageAspect */
6650
        $languageAspect = $context->getAspect('language');
6651
        if ($languageAspect->doOverlays() && !empty($localizationParentField)) {
6652
            // Sys language content is set to zero/-1 - and it is expected that whatever routine processes the output will
6653
            // OVERLAY the records with localized versions!
6654
            $languageQuery = $expressionBuilder->in($languageField, [0, -1]);
6655
            // Use this option to include records that don't have a default language counterpart ("free mode")
6656
            // (originalpointerfield is 0 and the language field contains the requested language)
6657
            if (isset($conf['includeRecordsWithoutDefaultTranslation']) || $conf['includeRecordsWithoutDefaultTranslation.']) {
6658
                $includeRecordsWithoutDefaultTranslation = isset($conf['includeRecordsWithoutDefaultTranslation.']) ?
6659
                    $this->stdWrap($conf['includeRecordsWithoutDefaultTranslation'], $conf['includeRecordsWithoutDefaultTranslation.']) : $conf['includeRecordsWithoutDefaultTranslation'];
6660
                $includeRecordsWithoutDefaultTranslation = trim($includeRecordsWithoutDefaultTranslation) !== '';
6661
            } else {
6662
                // Option was not explicitly set, check what's in for the language overlay type.
6663
                $includeRecordsWithoutDefaultTranslation = $languageAspect->getOverlayType() === $languageAspect::OVERLAYS_ON_WITH_FLOATING;
6664
            }
6665
            if ($includeRecordsWithoutDefaultTranslation) {
6666
                $languageQuery = $expressionBuilder->orX(
6667
                    $languageQuery,
6668
                    $expressionBuilder->andX(
6669
                        $expressionBuilder->eq($table . '.' . $localizationParentField, 0),
6670
                        $expressionBuilder->eq($languageField, $languageAspect->getContentId())
6671
                    )
6672
                );
6673
            }
6674
            return $languageQuery;
6675
        }
6676
        // No overlays = only fetch records given for the requested language and "all languages"
6677
        return $expressionBuilder->in($languageField, [$languageAspect->getContentId(), -1]);
6678
    }
6679
6680
    /**
6681
     * Helper function for getQuery, sanitizing the select part
6682
     *
6683
     * This functions checks if the necessary fields are part of the select
6684
     * and adds them if necessary.
6685
     *
6686
     * @param string $selectPart Select part
6687
     * @param string $table Table to select from
6688
     * @return string Sanitized select part
6689
     * @internal
6690
     * @see getQuery
6691
     */
6692
    protected function sanitizeSelectPart($selectPart, $table)
6693
    {
6694
        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
6695
6696
        // Pattern matching parts
6697
        $matchStart = '/(^\\s*|,\\s*|' . $table . '\\.)';
6698
        $matchEnd = '(\\s*,|\\s*$)/';
6699
        $necessaryFields = ['uid', 'pid'];
6700
        $wsFields = ['t3ver_state'];
6701
        if (isset($GLOBALS['TCA'][$table]) && !preg_match($matchStart . '\\*' . $matchEnd, $selectPart) && !preg_match('/(count|max|min|avg|sum)\\([^\\)]+\\)|distinct/i', $selectPart)) {
6702
            foreach ($necessaryFields as $field) {
6703
                $match = $matchStart . $field . $matchEnd;
6704
                if (!preg_match($match, $selectPart)) {
6705
                    $selectPart .= ', ' . $connection->quoteIdentifier($table . '.' . $field) . ' AS ' . $connection->quoteIdentifier($field);
6706
                }
6707
            }
6708
            if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
6709
                foreach ($wsFields as $field) {
6710
                    $match = $matchStart . $field . $matchEnd;
6711
                    if (!preg_match($match, $selectPart)) {
6712
                        $selectPart .= ', ' . $connection->quoteIdentifier($table . '.' . $field) . ' AS ' . $connection->quoteIdentifier($field);
6713
                    }
6714
                }
6715
            }
6716
        }
6717
        return $selectPart;
6718
    }
6719
6720
    /**
6721
     * 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)
6722
     *
6723
     * @param int[] $pageIds Array of Page UID numbers for select and for which pages with enablefields and bad doktypes should be removed.
6724
     * @return array Returns the array of remaining page UID numbers
6725
     * @internal
6726
     */
6727
    public function checkPidArray($pageIds)
6728
    {
6729
        if (!is_array($pageIds) || empty($pageIds)) {
0 ignored issues
show
introduced by
The condition is_array($pageIds) is always true.
Loading history...
6730
            return [];
6731
        }
6732
        $restrictionContainer = GeneralUtility::makeInstance(FrontendRestrictionContainer::class);
6733
        $restrictionContainer->add(GeneralUtility::makeInstance(
6734
            DocumentTypeExclusionRestriction::class,
6735
            GeneralUtility::intExplode(',', $this->checkPid_badDoktypeList, true)
6736
        ));
6737
        return $this->getTypoScriptFrontendController()->sys_page->filterAccessiblePageIds($pageIds, $restrictionContainer);
6738
    }
6739
6740
    /**
6741
     * Builds list of marker values for handling PDO-like parameter markers in select parts.
6742
     * 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.
6743
     *
6744
     * @param string $table Table to select records from
6745
     * @param array $conf Select part of CONTENT definition
6746
     * @return array List of values to replace markers with
6747
     * @internal
6748
     * @see getQuery()
6749
     */
6750
    public function getQueryMarkers($table, $conf)
6751
    {
6752
        if (!is_array($conf['markers.'])) {
6753
            return [];
6754
        }
6755
        // Parse markers and prepare their values
6756
        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
6757
        $markerValues = [];
6758
        foreach ($conf['markers.'] as $dottedMarker => $dummy) {
6759
            $marker = rtrim($dottedMarker, '.');
6760
            if ($dottedMarker != $marker . '.') {
6761
                continue;
6762
            }
6763
            // Parse definition
6764
            $tempValue = isset($conf['markers.'][$dottedMarker])
6765
                ? $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...
6766
                : $conf['markers.'][$dottedMarker]['value'];
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
6767
            // Quote/escape if needed
6768
            if (is_numeric($tempValue)) {
6769
                if ((int)$tempValue == $tempValue) {
6770
                    // Handle integer
6771
                    $markerValues[$marker] = (int)$tempValue;
6772
                } else {
6773
                    // Handle float
6774
                    $markerValues[$marker] = (float)$tempValue;
6775
                }
6776
            } elseif ($tempValue === null) {
6777
                // It represents NULL
6778
                $markerValues[$marker] = 'NULL';
6779
            } elseif (!empty($conf['markers.'][$dottedMarker]['commaSeparatedList'])) {
6780
                // See if it is really a comma separated list of values
6781
                $explodeValues = GeneralUtility::trimExplode(',', $tempValue);
6782
                if (count($explodeValues) > 1) {
6783
                    // Handle each element of list separately
6784
                    $tempArray = [];
6785
                    foreach ($explodeValues as $listValue) {
6786
                        if (is_numeric($listValue)) {
6787
                            if ((int)$listValue == $listValue) {
6788
                                $tempArray[] = (int)$listValue;
6789
                            } else {
6790
                                $tempArray[] = (float)$listValue;
6791
                            }
6792
                        } else {
6793
                            // If quoted, remove quotes before
6794
                            // escaping.
6795
                            if (preg_match('/^\'([^\']*)\'$/', $listValue, $matches)) {
6796
                                $listValue = $matches[1];
6797
                            } elseif (preg_match('/^\\"([^\\"]*)\\"$/', $listValue, $matches)) {
6798
                                $listValue = $matches[1];
6799
                            }
6800
                            $tempArray[] = $connection->quote($listValue);
6801
                        }
6802
                    }
6803
                    $markerValues[$marker] = implode(',', $tempArray);
6804
                } else {
6805
                    // Handle remaining values as string
6806
                    $markerValues[$marker] = $connection->quote($tempValue);
6807
                }
6808
            } else {
6809
                // Handle remaining values as string
6810
                $markerValues[$marker] = $connection->quote($tempValue);
6811
            }
6812
        }
6813
        return $markerValues;
6814
    }
6815
6816
    /***********************************************
6817
     *
6818
     * Frontend editing functions
6819
     *
6820
     ***********************************************/
6821
    /**
6822
     * 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.
6823
     * With the "edit panel" the user will see buttons with links to editing, moving, hiding, deleting the element
6824
     * This function is used for the cObject EDITPANEL and the stdWrap property ".editPanel"
6825
     *
6826
     * @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.
6827
     * @param array $conf TypoScript configuration properties for the editPanel
6828
     * @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
6829
     * @param array $dataArray Alternative data array to use. Default is $this->data
6830
     * @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.
6831
     */
6832
    public function editPanel($content, $conf, $currentRecord = '', $dataArray = [])
6833
    {
6834
        if (!$this->getTypoScriptFrontendController()->isBackendUserLoggedIn()) {
6835
            return $content;
6836
        }
6837
        if (!$this->getTypoScriptFrontendController()->displayEditIcons) {
6838
            return $content;
6839
        }
6840
6841
        if (!$currentRecord) {
6842
            $currentRecord = $this->currentRecord;
6843
        }
6844
        if (empty($dataArray)) {
6845
            $dataArray = $this->data;
6846
        }
6847
6848
        if ($conf['newRecordFromTable']) {
6849
            $currentRecord = $conf['newRecordFromTable'] . ':NEW';
6850
            $conf['allow'] = 'new';
6851
            $checkEditAccessInternals = false;
6852
        } else {
6853
            $checkEditAccessInternals = true;
6854
        }
6855
        [$table, $uid] = explode(':', $currentRecord);
6856
        // Page ID for new records, 0 if not specified
6857
        $newRecordPid = (int)$conf['newRecordInPid'];
6858
        $newUid = null;
6859
        if (!$conf['onlyCurrentPid'] || $dataArray['pid'] == $this->getTypoScriptFrontendController()->id) {
6860
            if ($table === 'pages') {
6861
                $newUid = $uid;
6862
            } else {
6863
                if ($conf['newRecordFromTable']) {
6864
                    $newUid = $this->getTypoScriptFrontendController()->id;
6865
                    if ($newRecordPid) {
6866
                        $newUid = $newRecordPid;
6867
                    }
6868
                } else {
6869
                    $newUid = -1 * $uid;
6870
                }
6871
            }
6872
        }
6873
        if ($table && $this->getFrontendBackendUser()->allowedToEdit($table, $dataArray, $conf, $checkEditAccessInternals) && $this->getFrontendBackendUser()->allowedToEditLanguage($table, $dataArray)) {
6874
            $editClass = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/classes/class.frontendedit.php']['edit'];
6875
            if ($editClass) {
6876
                $edit = GeneralUtility::makeInstance($editClass);
6877
                $allowedActions = $this->getFrontendBackendUser()->getAllowedEditActions($table, $conf, $dataArray['pid']);
6878
                $content = $edit->editPanel($content, $conf, $currentRecord, $dataArray, $table, $allowedActions, $newUid, []);
6879
            }
6880
        }
6881
        return $content;
6882
    }
6883
6884
    /**
6885
     * 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.
6886
     * This implements TYPO3 context sensitive editing facilities. Only backend users will have access (if properly configured as well).
6887
     *
6888
     * @param string $content The content to which the edit icons should be appended
6889
     * @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
6890
     * @param array $conf TypoScript properties for configuring the edit icons.
6891
     * @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
6892
     * @param array $dataArray Alternative data array to use. Default is $this->data
6893
     * @param string $addUrlParamStr Additional URL parameters for the link pointing to FormEngine
6894
     * @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.
6895
     */
6896
    public function editIcons($content, $params, array $conf = [], $currentRecord = '', $dataArray = [], $addUrlParamStr = '')
6897
    {
6898
        if (!$this->getTypoScriptFrontendController()->isBackendUserLoggedIn()) {
6899
            return $content;
6900
        }
6901
        if (!$this->getTypoScriptFrontendController()->displayFieldEditIcons) {
6902
            return $content;
6903
        }
6904
        if (!$currentRecord) {
6905
            $currentRecord = $this->currentRecord;
6906
        }
6907
        if (empty($dataArray)) {
6908
            $dataArray = $this->data;
6909
        }
6910
        // Check incoming params:
6911
        [$currentRecordTable, $currentRecordUID] = explode(':', $currentRecord);
6912
        [$fieldList, $table] = array_reverse(GeneralUtility::trimExplode(':', $params, true));
6913
        // Reverse the array because table is optional
6914
        if (!$table) {
6915
            $table = $currentRecordTable;
6916
        } elseif ($table != $currentRecordTable) {
6917
            // If the table is set as the first parameter, and does not match the table of the current record, then just return.
6918
            return $content;
6919
        }
6920
6921
        $editUid = $dataArray['_LOCALIZED_UID'] ?: $currentRecordUID;
6922
        // Edit icons imply that the editing action is generally allowed, assuming page and content element permissions permit it.
6923
        if (!array_key_exists('allow', $conf)) {
6924
            $conf['allow'] = 'edit';
6925
        }
6926
        if ($table && $this->getFrontendBackendUser()->allowedToEdit($table, $dataArray, $conf, true) && $fieldList && $this->getFrontendBackendUser()->allowedToEditLanguage($table, $dataArray)) {
6927
            $editClass = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/classes/class.frontendedit.php']['edit'];
6928
            if ($editClass) {
6929
                $edit = GeneralUtility::makeInstance($editClass);
6930
                $content = $edit->editIcons($content, $params, $conf, $currentRecord, $dataArray, $addUrlParamStr, $table, $editUid, $fieldList);
6931
            }
6932
        }
6933
        return $content;
6934
    }
6935
6936
    /**
6937
     * Returns TRUE if the input table/row would be hidden in the frontend (according nto the current time and simulate user group)
6938
     *
6939
     * @param string $table The table name
6940
     * @param array $row The data record
6941
     * @return bool
6942
     * @internal
6943
     * @see editPanelPreviewBorder()
6944
     */
6945
    public function isDisabled($table, $row)
6946
    {
6947
        $tsfe = $this->getTypoScriptFrontendController();
6948
        $enablecolumns = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns'];
6949
        return $enablecolumns['disabled'] && $row[$enablecolumns['disabled']]
6950
            || $enablecolumns['fe_group'] && $tsfe->simUserGroup && (int)$row[$enablecolumns['fe_group']] === (int)$tsfe->simUserGroup
6951
            || $enablecolumns['starttime'] && $row[$enablecolumns['starttime']] > $GLOBALS['EXEC_TIME']
6952
            || $enablecolumns['endtime'] && $row[$enablecolumns['endtime']] && $row[$enablecolumns['endtime']] < $GLOBALS['EXEC_TIME'];
6953
    }
6954
6955
    /**
6956
     * Get instance of FAL resource factory
6957
     *
6958
     * @return ResourceFactory
6959
     */
6960
    protected function getResourceFactory()
6961
    {
6962
        return GeneralUtility::makeInstance(ResourceFactory::class);
6963
    }
6964
6965
    /**
6966
     * Wrapper function for GeneralUtility::getIndpEnv()
6967
     *
6968
     * @see GeneralUtility::getIndpEnv
6969
     * @param string $key Name of the "environment variable"/"server variable" you wish to get.
6970
     * @return string
6971
     */
6972
    protected function getEnvironmentVariable($key)
6973
    {
6974
        return GeneralUtility::getIndpEnv($key);
6975
    }
6976
6977
    /**
6978
     * Fetches content from cache
6979
     *
6980
     * @param array $configuration Array
6981
     * @return string|bool FALSE on cache miss
6982
     * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
6983
     */
6984
    protected function getFromCache(array $configuration)
6985
    {
6986
        $content = false;
6987
6988
        if ($this->getTypoScriptFrontendController()->no_cache) {
6989
            return $content;
6990
        }
6991
        $cacheKey = $this->calculateCacheKey($configuration);
6992
        if (!empty($cacheKey)) {
6993
            /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cacheFrontend */
6994
            $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)
6995
                ->getCache('hash');
6996
            $content = $cacheFrontend->get($cacheKey);
6997
        }
6998
        return $content;
6999
    }
7000
7001
    /**
7002
     * Calculates the lifetime of a cache entry based on the given configuration
7003
     *
7004
     * @param array $configuration
7005
     * @return int|null
7006
     */
7007
    protected function calculateCacheLifetime(array $configuration)
7008
    {
7009
        $lifetimeConfiguration = $configuration['lifetime'] ?? '';
7010
        $lifetimeConfiguration = isset($configuration['lifetime.'])
7011
            ? $this->stdWrap($lifetimeConfiguration, $configuration['lifetime.'])
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
7012
            : $lifetimeConfiguration;
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
7013
7014
        $lifetime = null; // default lifetime
7015
        if (strtolower($lifetimeConfiguration) === 'unlimited') {
7016
            $lifetime = 0; // unlimited
7017
        } elseif ($lifetimeConfiguration > 0) {
7018
            $lifetime = (int)$lifetimeConfiguration; // lifetime in seconds
7019
        }
7020
        return $lifetime;
7021
    }
7022
7023
    /**
7024
     * Calculates the tags for a cache entry bases on the given configuration
7025
     *
7026
     * @param array $configuration
7027
     * @return array
7028
     */
7029
    protected function calculateCacheTags(array $configuration)
7030
    {
7031
        $tags = $configuration['tags'] ?? '';
7032
        $tags = isset($configuration['tags.'])
7033
            ? $this->stdWrap($tags, $configuration['tags.'])
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
7034
            : $tags;
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
7035
        return empty($tags) ? [] : GeneralUtility::trimExplode(',', $tags);
7036
    }
7037
7038
    /**
7039
     * Applies stdWrap to the cache key
7040
     *
7041
     * @param array $configuration
7042
     * @return string
7043
     */
7044
    protected function calculateCacheKey(array $configuration)
7045
    {
7046
        $key = $configuration['key'] ?? '';
7047
        return isset($configuration['key.'])
7048
            ? $this->stdWrap($key, $configuration['key.'])
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
7049
            : $key;
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
7050
    }
7051
7052
    /**
7053
     * Returns the current BE user.
7054
     *
7055
     * @return \TYPO3\CMS\Backend\FrontendBackendUserAuthentication
7056
     */
7057
    protected function getFrontendBackendUser()
7058
    {
7059
        return $GLOBALS['BE_USER'];
7060
    }
7061
7062
    /**
7063
     * @return TimeTracker
7064
     */
7065
    protected function getTimeTracker()
7066
    {
7067
        return GeneralUtility::makeInstance(TimeTracker::class);
7068
    }
7069
7070
    /**
7071
     * @return \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
7072
     */
7073
    protected function getTypoScriptFrontendController()
7074
    {
7075
        return $this->typoScriptFrontendController ?: $GLOBALS['TSFE'];
7076
    }
7077
7078
    /**
7079
     * Support anchors without href value
7080
     * Changes ContentObjectRenderer::typolink to render a tag without href,
7081
     * if id or name attribute is present.
7082
     *
7083
     * @param string $linkText
7084
     * @param array $conf Typolink configuration decoded as array
7085
     * @return string Full a-Tag or just the linktext if id or name are not set.
7086
     */
7087
    protected function resolveAnchorLink(string $linkText, array $conf): string
7088
    {
7089
        $anchorTag = '<a ' . $this->getATagParams($conf) . '>';
7090
        $aTagParams = GeneralUtility::get_tag_attributes($anchorTag);
7091
        // If it looks like a anchor tag, render it anyway
7092
        if (isset($aTagParams['id']) || isset($aTagParams['name'])) {
7093
            return $anchorTag . $linkText . '</a>';
7094
        }
7095
        // Otherwise just return the link text
7096
        return $linkText;
7097
    }
7098
7099
    /**
7100
     * Get content length of the current tag that could also contain nested tag contents
7101
     *
7102
     * @param string $theValue
7103
     * @param int $pointer
7104
     * @param string $currentTag
7105
     * @return int
7106
     */
7107
    protected function getContentLengthOfCurrentTag(string $theValue, int $pointer, string $currentTag): int
7108
    {
7109
        $tempContent = strtolower(substr($theValue, $pointer));
7110
        $startTag = '<' . $currentTag;
7111
        $endTag = '</' . $currentTag . '>';
7112
        $offsetCount = 0;
7113
7114
        // Take care for nested tags
7115
        do {
7116
            $nextMatchingEndTagPosition = strpos($tempContent, $endTag);
7117
            $nextSameTypeTagPosition = strpos($tempContent, $startTag);
7118
7119
            // filter out nested tag contents to help getting the correct closing tag
7120
            if ($nextSameTypeTagPosition !== false && $nextSameTypeTagPosition < $nextMatchingEndTagPosition) {
7121
                $lastOpeningTagStartPosition = strrpos(substr($tempContent, 0, $nextMatchingEndTagPosition), $startTag);
7122
                $closingTagEndPosition = $nextMatchingEndTagPosition + strlen($endTag);
7123
                $offsetCount += $closingTagEndPosition - $lastOpeningTagStartPosition;
7124
7125
                // replace content from latest tag start to latest tag end
7126
                $tempContent = substr($tempContent, 0, $lastOpeningTagStartPosition) . substr($tempContent, $closingTagEndPosition);
7127
            }
7128
        } while (
7129
            ($nextMatchingEndTagPosition !== false && $nextSameTypeTagPosition !== false) &&
7130
            $nextSameTypeTagPosition < $nextMatchingEndTagPosition
7131
        );
7132
7133
        // if no closing tag is found we use length of the whole content
7134
        $endingOffset = strlen($tempContent);
7135
        if ($nextMatchingEndTagPosition !== false) {
7136
            $endingOffset = $nextMatchingEndTagPosition + $offsetCount;
7137
        }
7138
7139
        return $endingOffset;
7140
    }
7141
}
7142