Completed
Push — master ( a088ad...55fcd8 )
by
unknown
23:17 queued 05:43
created

ContentObjectRenderer::decryptEmail()   B

Complexity

Conditions 10
Paths 3

Size

Total Lines 29
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 18
nc 3
nop 2
dl 0
loc 29
rs 7.6666
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A ContentObjectRenderer::encryptCharcode() 0 9 5

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
namespace TYPO3\CMS\Frontend\ContentObject;
3
4
/*
5
 * This file is part of the TYPO3 CMS project.
6
 *
7
 * It is free software; you can redistribute it and/or modify it under
8
 * the terms of the GNU General Public License, either version 2
9
 * of the License, or any later version.
10
 *
11
 * For the full copyright and license information, please read the
12
 * LICENSE.txt file that was distributed with this source code.
13
 *
14
 * The TYPO3 project - inspiring people to share!
15
 */
16
17
use Doctrine\DBAL\DBALException;
18
use Doctrine\DBAL\Driver\Statement;
19
use Psr\Container\ContainerInterface;
20
use Psr\Log\LoggerAwareInterface;
21
use Psr\Log\LoggerAwareTrait;
22
use Symfony\Component\Mime\Address;
23
use TYPO3\CMS\Core\Authentication\AbstractUserAuthentication;
24
use TYPO3\CMS\Core\Cache\CacheManager;
25
use TYPO3\CMS\Core\Context\Context;
26
use TYPO3\CMS\Core\Context\LanguageAspect;
27
use TYPO3\CMS\Core\Core\Environment;
28
use TYPO3\CMS\Core\Database\Connection;
29
use TYPO3\CMS\Core\Database\ConnectionPool;
30
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
31
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
32
use TYPO3\CMS\Core\Database\Query\QueryHelper;
33
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
34
use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
35
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
36
use TYPO3\CMS\Core\Html\HtmlParser;
37
use TYPO3\CMS\Core\Imaging\ImageManipulation\Area;
38
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
39
use TYPO3\CMS\Core\LinkHandling\LinkService;
40
use TYPO3\CMS\Core\Log\LogManager;
41
use TYPO3\CMS\Core\Mail\MailMessage;
42
use TYPO3\CMS\Core\Page\AssetCollector;
43
use TYPO3\CMS\Core\Resource\Exception;
44
use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
45
use TYPO3\CMS\Core\Resource\File;
46
use TYPO3\CMS\Core\Resource\FileInterface;
47
use TYPO3\CMS\Core\Resource\FileReference;
48
use TYPO3\CMS\Core\Resource\Folder;
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
     * This is used by checkPid, that checks if pages are accessible. The $checkPid_cache['page_uid'] is set TRUE or FALSE upon this check featuring a caching function for the next request.
355
     *
356
     * @var array
357
     */
358
    public $checkPid_cache = [];
359
360
    /**
361
     * @var string|int
362
     */
363
    public $checkPid_badDoktypeList = PageRepository::DOKTYPE_RECYCLER;
364
365
    /**
366
     * This will be set by typoLink() to the url of the most recent link created.
367
     *
368
     * @var string
369
     */
370
    public $lastTypoLinkUrl = '';
371
372
    /**
373
     * DO. link target.
374
     *
375
     * @var string
376
     */
377
    public $lastTypoLinkTarget = '';
378
379
    /**
380
     * @var array
381
     */
382
    public $lastTypoLinkLD = [];
383
384
    /**
385
     * array that registers rendered content elements (or any table) to make sure they are not rendered recursively!
386
     *
387
     * @var array
388
     */
389
    public $recordRegister = [];
390
391
    /**
392
     * Additionally registered content object types and class names
393
     *
394
     * @var array
395
     */
396
    protected $cObjHookObjectsRegistry = [];
397
398
    /**
399
     * @var array
400
     */
401
    public $cObjHookObjectsArr = [];
402
403
    /**
404
     * Containing hook objects for stdWrap
405
     *
406
     * @var array
407
     */
408
    protected $stdWrapHookObjects = [];
409
410
    /**
411
     * Containing hook objects for getImgResource
412
     *
413
     * @var array
414
     */
415
    protected $getImgResourceHookObjects;
416
417
    /**
418
     * @var File Current file objects (during iterations over files)
419
     */
420
    protected $currentFile;
421
422
    /**
423
     * Set to TRUE by doConvertToUserIntObject() if USER object wants to become USER_INT
424
     * @var bool
425
     */
426
    public $doConvertToUserIntObject = false;
427
428
    /**
429
     * Indicates current object type. Can hold one of OBJECTTYPE_ constants or FALSE.
430
     * The value is set and reset inside USER() function. Any time outside of
431
     * USER() it is FALSE.
432
     * @var bool
433
     */
434
    protected $userObjectType = false;
435
436
    /**
437
     * @var array
438
     */
439
    protected $stopRendering = [];
440
441
    /**
442
     * @var int
443
     */
444
    protected $stdWrapRecursionLevel = 0;
445
446
    /**
447
     * @var TypoScriptFrontendController
448
     */
449
    protected $typoScriptFrontendController;
450
451
    /**
452
     * Indicates that object type is USER.
453
     *
454
     * @see ContentObjectRender::$userObjectType
455
     */
456
    const OBJECTTYPE_USER_INT = 1;
457
    /**
458
     * Indicates that object type is USER.
459
     *
460
     * @see ContentObjectRender::$userObjectType
461
     */
462
    const OBJECTTYPE_USER = 2;
463
464
    /**
465
     * @param TypoScriptFrontendController $typoScriptFrontendController
466
     * @param ContainerInterface $container
467
     */
468
    public function __construct(TypoScriptFrontendController $typoScriptFrontendController = null, ContainerInterface $container = null)
469
    {
470
        $this->typoScriptFrontendController = $typoScriptFrontendController;
471
        $this->contentObjectClassMap = $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'];
472
        $this->container = $container;
473
    }
474
475
    /**
476
     * Prevent several objects from being serialized.
477
     * If currentFile is set, it is either a File or a FileReference object. As the object itself can't be serialized,
478
     * we have store a hash and restore the object in __wakeup()
479
     *
480
     * @return array
481
     */
482
    public function __sleep()
483
    {
484
        $vars = get_object_vars($this);
485
        unset($vars['typoScriptFrontendController'], $vars['logger'], $vars['container']);
486
        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...
487
            $this->currentFile = 'FileReference:' . $this->currentFile->getUid();
488
        } 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...
489
            $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...
490
        } else {
491
            unset($vars['currentFile']);
492
        }
493
        return array_keys($vars);
494
    }
495
496
    /**
497
     * Restore currentFile from hash.
498
     * If currentFile references a File, the identifier equals file identifier.
499
     * If it references a FileReference the identifier equals the uid of the reference.
500
     */
501
    public function __wakeup()
502
    {
503
        if (isset($GLOBALS['TSFE'])) {
504
            $this->typoScriptFrontendController = $GLOBALS['TSFE'];
505
        }
506
        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...
507
            [$objectType, $identifier] = explode(':', $this->currentFile, 2);
508
            try {
509
                if ($objectType === 'File') {
510
                    $this->currentFile = GeneralUtility::makeInstance(ResourceFactory::class)->retrieveFileOrFolderObject($identifier);
511
                } elseif ($objectType === 'FileReference') {
512
                    $this->currentFile = GeneralUtility::makeInstance(ResourceFactory::class)->getFileReferenceObject($identifier);
513
                }
514
            } catch (ResourceDoesNotExistException $e) {
515
                $this->currentFile = null;
516
            }
517
        }
518
        $this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
519
        $this->container = GeneralUtility::getContainer();
520
    }
521
522
    /**
523
     * Allow injecting content object class map.
524
     *
525
     * This method is private API, please use configuration
526
     * $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'] to add new content objects
527
     *
528
     * @internal
529
     * @param array $contentObjectClassMap
530
     */
531
    public function setContentObjectClassMap(array $contentObjectClassMap)
532
    {
533
        $this->contentObjectClassMap = $contentObjectClassMap;
534
    }
535
536
    /**
537
     * Register a single content object name to class name
538
     *
539
     * This method is private API, please use configuration
540
     * $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'] to add new content objects
541
     *
542
     * @param string $className
543
     * @param string $contentObjectName
544
     * @internal
545
     */
546
    public function registerContentObjectClass($className, $contentObjectName)
547
    {
548
        $this->contentObjectClassMap[$contentObjectName] = $className;
549
    }
550
551
    /**
552
     * Class constructor.
553
     * Well, it has to be called manually since it is not a real constructor function.
554
     * 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.
555
     *
556
     * @param array $data The record data that is rendered.
557
     * @param string $table The table that the data record is from.
558
     */
559
    public function start($data, $table = '')
560
    {
561
        $this->data = $data;
562
        $this->table = $table;
563
        $this->currentRecord = $table !== ''
564
            ? $table . ':' . ($this->data['uid'] ?? '')
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
565
            : '';
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
566
        $this->parameters = [];
567
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['cObjTypeAndClass'] ?? [] as $classArr) {
568
            $this->cObjHookObjectsRegistry[$classArr[0]] = $classArr[1];
569
        }
570
        $this->stdWrapHookObjects = [];
571
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap'] ?? [] as $className) {
572
            $hookObject = GeneralUtility::makeInstance($className);
573
            if (!$hookObject instanceof ContentObjectStdWrapHookInterface) {
574
                throw new \UnexpectedValueException($className . ' must implement interface ' . ContentObjectStdWrapHookInterface::class, 1195043965);
575
            }
576
            $this->stdWrapHookObjects[] = $hookObject;
577
        }
578
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['postInit'] ?? [] as $className) {
579
            $postInitializationProcessor = GeneralUtility::makeInstance($className);
580
            if (!$postInitializationProcessor instanceof ContentObjectPostInitHookInterface) {
581
                throw new \UnexpectedValueException($className . ' must implement interface ' . ContentObjectPostInitHookInterface::class, 1274563549);
582
            }
583
            $postInitializationProcessor->postProcessContentObjectInitialization($this);
584
        }
585
    }
586
587
    /**
588
     * Returns the current table
589
     *
590
     * @return string
591
     */
592
    public function getCurrentTable()
593
    {
594
        return $this->table;
595
    }
596
597
    /**
598
     * Gets the 'getImgResource' hook objects.
599
     * The first call initializes the accordant objects.
600
     *
601
     * @return array The 'getImgResource' hook objects (if any)
602
     */
603
    protected function getGetImgResourceHookObjects()
604
    {
605
        if (!isset($this->getImgResourceHookObjects)) {
606
            $this->getImgResourceHookObjects = [];
607
            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImgResource'] ?? [] as $className) {
608
                $hookObject = GeneralUtility::makeInstance($className);
609
                if (!$hookObject instanceof ContentObjectGetImageResourceHookInterface) {
610
                    throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetImageResourceHookInterface::class, 1218636383);
611
                }
612
                $this->getImgResourceHookObjects[] = $hookObject;
613
            }
614
        }
615
        return $this->getImgResourceHookObjects;
616
    }
617
618
    /**
619
     * Sets the internal variable parentRecord with information about current record.
620
     * 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.
621
     *
622
     * @param array $data The record array
623
     * @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.
624
     * @internal
625
     */
626
    public function setParent($data, $currentRecord)
627
    {
628
        $this->parentRecord = [
629
            'data' => $data,
630
            'currentRecord' => $currentRecord
631
        ];
632
    }
633
634
    /***********************************************
635
     *
636
     * CONTENT_OBJ:
637
     *
638
     ***********************************************/
639
    /**
640
     * Returns the "current" value.
641
     * 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.
642
     * It's like "load accumulator" in the good old C64 days... basically a "register" you can use as you like.
643
     * 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.
644
     *
645
     * @return mixed The "current" value
646
     */
647
    public function getCurrentVal()
648
    {
649
        return $this->data[$this->currentValKey];
650
    }
651
652
    /**
653
     * Sets the "current" value.
654
     *
655
     * @param mixed $value The variable that you want to set as "current
656
     * @see getCurrentVal()
657
     */
658
    public function setCurrentVal($value)
659
    {
660
        $this->data[$this->currentValKey] = $value;
661
    }
662
663
    /**
664
     * Rendering of a "numerical array" of cObjects from TypoScript
665
     * Will call ->cObjGetSingle() for each cObject found and accumulate the output.
666
     *
667
     * @param array $setup array with cObjects as values.
668
     * @param string $addKey A prefix for the debugging information
669
     * @return string Rendered output from the cObjects in the array.
670
     * @see cObjGetSingle()
671
     */
672
    public function cObjGet($setup, $addKey = '')
673
    {
674
        if (!is_array($setup)) {
0 ignored issues
show
introduced by
The condition is_array($setup) is always true.
Loading history...
675
            return '';
676
        }
677
        $sKeyArray = ArrayUtility::filterAndSortByNumericKeys($setup);
678
        $content = '';
679
        foreach ($sKeyArray as $theKey) {
680
            $theValue = $setup[$theKey];
681
            if ((int)$theKey && strpos($theKey, '.') === false) {
682
                $conf = $setup[$theKey . '.'];
683
                $content .= $this->cObjGetSingle($theValue, $conf, $addKey . $theKey);
684
            }
685
        }
686
        return $content;
687
    }
688
689
    /**
690
     * Renders a content object
691
     *
692
     * @param string $name The content object name, eg. "TEXT" or "USER" or "IMAGE"
693
     * @param array $conf The array with TypoScript properties for the content object
694
     * @param string $TSkey A string label used for the internal debugging tracking.
695
     * @return string cObject output
696
     * @throws \UnexpectedValueException
697
     */
698
    public function cObjGetSingle($name, $conf, $TSkey = '__')
699
    {
700
        $content = '';
701
        // Checking that the function is not called eternally. This is done by interrupting at a depth of 100
702
        $this->getTypoScriptFrontendController()->cObjectDepthCounter--;
703
        if ($this->getTypoScriptFrontendController()->cObjectDepthCounter > 0) {
704
            $timeTracker = $this->getTimeTracker();
705
            $name = trim($name);
706
            if ($timeTracker->LR) {
707
                $timeTracker->push($TSkey, $name);
708
            }
709
            // Checking if the COBJ is a reference to another object. (eg. name of 'some.object =< styles.something')
710
            if (isset($name[0]) && $name[0] === '<') {
711
                $key = trim(substr($name, 1));
712
                $cF = GeneralUtility::makeInstance(TypoScriptParser::class);
713
                // $name and $conf is loaded with the referenced values.
714
                $confOverride = is_array($conf) ? $conf : [];
0 ignored issues
show
introduced by
The condition is_array($conf) is always true.
Loading history...
715
                [$name, $conf] = $cF->getVal($key, $this->getTypoScriptFrontendController()->tmpl->setup);
716
                $conf = array_replace_recursive(is_array($conf) ? $conf : [], $confOverride);
717
                // Getting the cObject
718
                $timeTracker->incStackPointer();
719
                $content .= $this->cObjGetSingle($name, $conf, $key);
720
                $timeTracker->decStackPointer();
721
            } else {
722
                $hooked = false;
723
                // Application defined cObjects
724
                if (!empty($this->cObjHookObjectsRegistry[$name])) {
725
                    if (empty($this->cObjHookObjectsArr[$name])) {
726
                        $this->cObjHookObjectsArr[$name] = GeneralUtility::makeInstance($this->cObjHookObjectsRegistry[$name]);
727
                    }
728
                    $hookObj = $this->cObjHookObjectsArr[$name];
729
                    if (method_exists($hookObj, 'cObjGetSingleExt')) {
730
                        $content .= $hookObj->cObjGetSingleExt($name, $conf, $TSkey, $this);
731
                        $hooked = true;
732
                    }
733
                }
734
                if (!$hooked) {
735
                    $contentObject = $this->getContentObject($name);
736
                    if ($contentObject) {
737
                        $content .= $this->render($contentObject, $conf);
738
                    } else {
739
                        // Call hook functions for extra processing
740
                        if ($name) {
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) {
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);
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
     * @internal
1050
     * @see IMAGE()
1051
     */
1052
    public function cImage($file, $conf)
1053
    {
1054
        $tsfe = $this->getTypoScriptFrontendController();
1055
        $info = $this->getImgResource($file, $conf['file.']);
1056
        $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...
1057
        if (!is_array($info)) {
1058
            return '';
1059
        }
1060
        if (is_file(Environment::getPublicPath() . '/' . $info['3'])) {
1061
            $source = $tsfe->absRefPrefix . str_replace('%2F', '/', rawurlencode($info['3']));
1062
        } else {
1063
            $source = $info[3];
1064
        }
1065
        // Remove file objects for AssetCollector, as it only allows to store scalar values
1066
        unset($info['originalFile'], $info['processedFile']);
1067
        GeneralUtility::makeInstance(AssetCollector::class)->addMedia(
1068
            $source,
1069
            $info
1070
        );
1071
1072
        $layoutKey = $this->stdWrap($conf['layoutKey'], $conf['layoutKey.']);
1073
        $imageTagTemplate = $this->getImageTagTemplate($layoutKey, $conf);
1074
        $sourceCollection = $this->getImageSourceCollection($layoutKey, $conf, $file);
1075
1076
        // This array is used to collect the image-refs on the page...
1077
        $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...
1078
        $altParam = $this->getAltParam($conf);
1079
        $params = $this->stdWrapValue('params', $conf);
1080
        if ($params !== '' && $params[0] !== ' ') {
1081
            $params = ' ' . $params;
1082
        }
1083
1084
        $imageTagValues = [
1085
            'width' =>  (int)$info[0],
1086
            'height' => (int)$info[1],
1087
            'src' => htmlspecialchars($source),
1088
            'params' => $params,
1089
            'altParams' => $altParam,
1090
            'border' =>  $this->getBorderAttr(' border="' . (int)$conf['border'] . '"'),
1091
            'sourceCollection' => $sourceCollection,
1092
            'selfClosingTagSlash' => !empty($tsfe->xhtmlDoctype) ? ' /' : '',
1093
        ];
1094
1095
        $markerTemplateEngine = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
1096
        $theValue = $markerTemplateEngine->substituteMarkerArray($imageTagTemplate, $imageTagValues, '###|###', true, true);
1097
1098
        $linkWrap = isset($conf['linkWrap.']) ? $this->stdWrap($conf['linkWrap'], $conf['linkWrap.']) : $conf['linkWrap'];
1099
        if ($linkWrap) {
1100
            $theValue = $this->linkWrap($theValue, $linkWrap);
1101
        } elseif ($conf['imageLinkWrap']) {
1102
            $originalFile = !empty($info['originalFile']) ? $info['originalFile'] : $info['origFile'];
1103
            $theValue = $this->imageLinkWrap($theValue, $originalFile, $conf['imageLinkWrap.']);
1104
        }
1105
        $wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
1106
        if ((string)$wrap !== '') {
1107
            $theValue = $this->wrap($theValue, $conf['wrap']);
1108
        }
1109
        return $theValue;
1110
    }
1111
1112
    /**
1113
     * Returns the 'border' attribute for an <img> tag only if the doctype is not xhtml_strict, xhtml_11 or html5
1114
     * or if the config parameter 'disableImgBorderAttr' is not set.
1115
     *
1116
     * @param string $borderAttr The border attribute
1117
     * @return string The border attribute
1118
     */
1119
    public function getBorderAttr($borderAttr)
1120
    {
1121
        $tsfe = $this->getTypoScriptFrontendController();
1122
        $docType = $tsfe->xhtmlDoctype;
1123
        if (
1124
            $docType !== 'xhtml_strict' && $docType !== 'xhtml_11'
1125
            && $tsfe->config['config']['doctype'] !== 'html5'
1126
            && !$tsfe->config['config']['disableImgBorderAttr']
1127
        ) {
1128
            return $borderAttr;
1129
        }
1130
        return '';
1131
    }
1132
1133
    /**
1134
     * Returns the html-template for rendering the image-Tag if no template is defined via typoscript the
1135
     * default <img> tag template is returned
1136
     *
1137
     * @param string $layoutKey rendering key
1138
     * @param array $conf TypoScript configuration properties
1139
     * @return string
1140
     */
1141
    public function getImageTagTemplate($layoutKey, $conf)
1142
    {
1143
        if ($layoutKey && isset($conf['layout.']) && isset($conf['layout.'][$layoutKey . '.'])) {
1144
            $imageTagLayout = $this->stdWrap(
1145
                $conf['layout.'][$layoutKey . '.']['element'] ?? '',
1146
                $conf['layout.'][$layoutKey . '.']['element.'] ?? []
1147
            );
1148
        } else {
1149
            $imageTagLayout = '<img src="###SRC###" width="###WIDTH###" height="###HEIGHT###" ###PARAMS### ###ALTPARAMS### ###BORDER######SELFCLOSINGTAGSLASH###>';
1150
        }
1151
        return $imageTagLayout;
1152
    }
1153
1154
    /**
1155
     * Render alternate sources for the image tag. If no source collection is given an empty string is returned.
1156
     *
1157
     * @param string $layoutKey rendering key
1158
     * @param array $conf TypoScript configuration properties
1159
     * @param string $file
1160
     * @throws \UnexpectedValueException
1161
     * @return string
1162
     */
1163
    public function getImageSourceCollection($layoutKey, $conf, $file)
1164
    {
1165
        $sourceCollection = '';
1166
        if ($layoutKey
1167
            && isset($conf['sourceCollection.']) && $conf['sourceCollection.']
1168
            && (
1169
                isset($conf['layout.'][$layoutKey . '.']['source']) && $conf['layout.'][$layoutKey . '.']['source']
1170
                || isset($conf['layout.'][$layoutKey . '.']['source.']) && $conf['layout.'][$layoutKey . '.']['source.']
1171
            )
1172
        ) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
1173
1174
            // find active sourceCollection
1175
            $activeSourceCollections = [];
1176
            foreach ($conf['sourceCollection.'] as $sourceCollectionKey => $sourceCollectionConfiguration) {
1177
                if (substr($sourceCollectionKey, -1) === '.') {
1178
                    if (empty($sourceCollectionConfiguration['if.']) || $this->checkIf($sourceCollectionConfiguration['if.'])) {
1179
                        $activeSourceCollections[] = $sourceCollectionConfiguration;
1180
                    }
1181
                }
1182
            }
1183
1184
            // apply option split to configurations
1185
            $tsfe = $this->getTypoScriptFrontendController();
1186
            $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
1187
            $srcLayoutOptionSplitted = $typoScriptService->explodeConfigurationForOptionSplit((array)$conf['layout.'][$layoutKey . '.'], count($activeSourceCollections));
1188
1189
            // render sources
1190
            foreach ($activeSourceCollections as $key => $sourceConfiguration) {
1191
                $sourceLayout = $this->stdWrap(
1192
                    $srcLayoutOptionSplitted[$key]['source'] ?? '',
1193
                    $srcLayoutOptionSplitted[$key]['source.'] ?? []
1194
                );
1195
1196
                $sourceRenderConfiguration = [
1197
                    'file' => $file,
1198
                    'file.' => $conf['file.'] ?? null
1199
                ];
1200
1201
                if (isset($sourceConfiguration['quality']) || isset($sourceConfiguration['quality.'])) {
1202
                    $imageQuality = $sourceConfiguration['quality'] ?? '';
1203
                    if (isset($sourceConfiguration['quality.'])) {
1204
                        $imageQuality = $this->stdWrap($sourceConfiguration['quality'], $sourceConfiguration['quality.']);
1205
                    }
1206
                    if ($imageQuality) {
1207
                        $sourceRenderConfiguration['file.']['params'] = '-quality ' . (int)$imageQuality;
1208
                    }
1209
                }
1210
1211
                if (isset($sourceConfiguration['pixelDensity'])) {
1212
                    $pixelDensity = (int)$this->stdWrap(
1213
                        $sourceConfiguration['pixelDensity'] ?? '',
1214
                        $sourceConfiguration['pixelDensity.'] ?? []
1215
                    );
1216
                } else {
1217
                    $pixelDensity = 1;
1218
                }
1219
                $dimensionKeys = ['width', 'height', 'maxW', 'minW', 'maxH', 'minH', 'maxWidth', 'maxHeight', 'XY'];
1220
                foreach ($dimensionKeys as $dimensionKey) {
1221
                    $dimension = $this->stdWrap(
1222
                        $sourceConfiguration[$dimensionKey] ?? '',
1223
                        $sourceConfiguration[$dimensionKey . '.'] ?? []
1224
                    );
1225
                    if (!$dimension) {
1226
                        $dimension = $this->stdWrap(
1227
                            $conf['file.'][$dimensionKey] ?? '',
1228
                            $conf['file.'][$dimensionKey . '.'] ?? []
1229
                        );
1230
                    }
1231
                    if ($dimension) {
1232
                        if (strpos($dimension, 'c') !== false && ($dimensionKey === 'width' || $dimensionKey === 'height')) {
1233
                            $dimensionParts = explode('c', $dimension, 2);
1234
                            $dimension = ((int)$dimensionParts[0] * $pixelDensity) . 'c';
1235
                            if ($dimensionParts[1]) {
1236
                                $dimension .= $dimensionParts[1];
1237
                            }
1238
                        } elseif ($dimensionKey === 'XY') {
1239
                            $dimensionParts = GeneralUtility::intExplode(',', $dimension, false, 2);
1240
                            $dimension = $dimensionParts[0] * $pixelDensity;
1241
                            if ($dimensionParts[1]) {
1242
                                $dimension .= ',' . $dimensionParts[1] * $pixelDensity;
1243
                            }
1244
                        } else {
1245
                            $dimension = (int)$dimension * $pixelDensity;
1246
                        }
1247
                        $sourceRenderConfiguration['file.'][$dimensionKey] = $dimension;
1248
                        // Remove the stdWrap properties for dimension as they have been processed already above.
1249
                        unset($sourceRenderConfiguration['file.'][$dimensionKey . '.']);
1250
                    }
1251
                }
1252
                $sourceInfo = $this->getImgResource($sourceRenderConfiguration['file'], $sourceRenderConfiguration['file.']);
1253
                if ($sourceInfo) {
1254
                    $sourceConfiguration['width'] = $sourceInfo[0];
1255
                    $sourceConfiguration['height'] = $sourceInfo[1];
1256
                    $urlPrefix = '';
1257
                    if (parse_url($sourceInfo[3], PHP_URL_HOST) === null) {
1258
                        $urlPrefix = $tsfe->absRefPrefix;
1259
                    }
1260
                    $sourceConfiguration['src'] = htmlspecialchars($urlPrefix . $sourceInfo[3]);
1261
                    $sourceConfiguration['selfClosingTagSlash'] = !empty($tsfe->xhtmlDoctype) ? ' /' : '';
1262
1263
                    $markerTemplateEngine = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
1264
                    $oneSourceCollection = $markerTemplateEngine->substituteMarkerArray($sourceLayout, $sourceConfiguration, '###|###', true, true);
1265
1266
                    foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImageSourceCollection'] ?? [] as $className) {
1267
                        $hookObject = GeneralUtility::makeInstance($className);
1268
                        if (!$hookObject instanceof ContentObjectOneSourceCollectionHookInterface) {
1269
                            throw new \UnexpectedValueException(
1270
                                '$hookObject must implement interface ' . ContentObjectOneSourceCollectionHookInterface::class,
1271
                                1380007853
1272
                            );
1273
                        }
1274
                        $oneSourceCollection = $hookObject->getOneSourceCollection((array)$sourceRenderConfiguration, (array)$sourceConfiguration, $oneSourceCollection, $this);
1275
                    }
1276
1277
                    $sourceCollection .= $oneSourceCollection;
1278
                }
1279
            }
1280
        }
1281
        return $sourceCollection;
1282
    }
1283
1284
    /**
1285
     * Wraps the input string in link-tags that opens the image in a new window.
1286
     *
1287
     * @param string $string String to wrap, probably an <img> tag
1288
     * @param string|File|FileReference $imageFile The original image file
1289
     * @param array $conf TypoScript properties for the "imageLinkWrap" function
1290
     * @return string The input string, $string, wrapped as configured.
1291
     * @see cImage()
1292
     */
1293
    public function imageLinkWrap($string, $imageFile, $conf)
1294
    {
1295
        $string = (string)$string;
1296
        $enable = isset($conf['enable.']) ? $this->stdWrap($conf['enable'], $conf['enable.']) : $conf['enable'];
1297
        if (!$enable) {
1298
            return $string;
1299
        }
1300
        $content = (string)$this->typoLink($string, $conf['typolink.']);
1301
        if (isset($conf['file.'])) {
1302
            $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

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

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

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

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

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

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

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

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

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

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

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

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

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

3220
            return mb_substr($content, $options[0], /** @scrutinizer ignore-type */ $options[1], 'utf-8');
Loading history...
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

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

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

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

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

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

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

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

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

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

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

5746
            $mail->replyTo(/** @scrutinizer ignore-type */ $parsedReplyTo);
Loading history...
5747
        }
5748
        $message = trim($message);
5749
        if ($message !== '') {
5750
            // First line is subject
5751
            $messageParts = explode(LF, $message, 2);
5752
            $subject = trim($messageParts[0]);
5753
            $plainMessage = trim($messageParts[1]);
5754
            $parsedRecipients = MailUtility::parseAddresses($recipients);
5755
            if (!empty($parsedRecipients)) {
5756
                $mail->to(...$parsedRecipients)
5757
                    ->subject($subject)
5758
                    ->text($plainMessage);
5759
                $mail->send();
5760
            }
5761
            $parsedCc = MailUtility::parseAddresses($cc);
5762
            if (!empty($parsedCc)) {
5763
                $from = $mail->getFrom();
5764
                /** @var MailMessage $mail */
5765
                $mail = GeneralUtility::makeInstance(MailMessage::class);
5766
                if (!empty($parsedReplyTo)) {
5767
                    $mail->replyTo($parsedReplyTo);
5768
                }
5769
                $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

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

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

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

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