Completed
Push — master ( 5be7b8...d53f54 )
by
unknown
14:34
created

ContentObjectRenderer::stdWrap_split()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Frontend\ContentObject;
17
18
use Doctrine\DBAL\DBALException;
19
use Doctrine\DBAL\Driver\Statement;
20
use Psr\Container\ContainerInterface;
21
use Psr\Log\LoggerAwareInterface;
22
use Psr\Log\LoggerAwareTrait;
23
use TYPO3\CMS\Core\Authentication\AbstractUserAuthentication;
24
use TYPO3\CMS\Core\Cache\CacheManager;
25
use TYPO3\CMS\Core\Context\Context;
26
use TYPO3\CMS\Core\Context\LanguageAspect;
27
use TYPO3\CMS\Core\Core\Environment;
28
use TYPO3\CMS\Core\Database\ConnectionPool;
29
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
30
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
31
use TYPO3\CMS\Core\Database\Query\QueryHelper;
32
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
33
use TYPO3\CMS\Core\Database\Query\Restriction\DocumentTypeExclusionRestriction;
34
use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
35
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
36
use TYPO3\CMS\Core\Html\HtmlParser;
37
use TYPO3\CMS\Core\Imaging\ImageManipulation\Area;
38
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
39
use TYPO3\CMS\Core\LinkHandling\Exception\UnknownLinkHandlerException;
40
use TYPO3\CMS\Core\LinkHandling\LinkService;
41
use TYPO3\CMS\Core\Log\LogManager;
42
use TYPO3\CMS\Core\Page\AssetCollector;
43
use TYPO3\CMS\Core\Resource\Exception;
44
use TYPO3\CMS\Core\Resource\Exception\InvalidPathException;
45
use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
46
use TYPO3\CMS\Core\Resource\File;
47
use TYPO3\CMS\Core\Resource\FileInterface;
48
use TYPO3\CMS\Core\Resource\FileReference;
49
use TYPO3\CMS\Core\Resource\ProcessedFile;
50
use TYPO3\CMS\Core\Resource\ResourceFactory;
51
use TYPO3\CMS\Core\Service\DependencyOrderingService;
52
use TYPO3\CMS\Core\Service\FlexFormService;
53
use TYPO3\CMS\Core\Site\SiteFinder;
54
use TYPO3\CMS\Core\TimeTracker\TimeTracker;
55
use TYPO3\CMS\Core\Type\BitSet;
56
use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
57
use TYPO3\CMS\Core\TypoScript\TypoScriptService;
58
use TYPO3\CMS\Core\Utility\ArrayUtility;
59
use TYPO3\CMS\Core\Utility\DebugUtility;
60
use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException;
61
use TYPO3\CMS\Core\Utility\GeneralUtility;
62
use TYPO3\CMS\Core\Utility\HttpUtility;
63
use TYPO3\CMS\Core\Utility\MathUtility;
64
use TYPO3\CMS\Core\Utility\StringUtility;
65
use TYPO3\CMS\Core\Versioning\VersionState;
66
use TYPO3\CMS\Frontend\ContentObject\Exception\ContentRenderingException;
67
use TYPO3\CMS\Frontend\ContentObject\Exception\ExceptionHandlerInterface;
68
use TYPO3\CMS\Frontend\ContentObject\Exception\ProductionExceptionHandler;
69
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
70
use TYPO3\CMS\Frontend\Http\UrlProcessorInterface;
71
use TYPO3\CMS\Frontend\Imaging\GifBuilder;
72
use TYPO3\CMS\Frontend\Page\PageLayoutResolver;
73
use TYPO3\CMS\Frontend\Resource\FilePathSanitizer;
74
use TYPO3\CMS\Frontend\Service\TypoLinkCodecService;
75
use TYPO3\CMS\Frontend\Typolink\AbstractTypolinkBuilder;
76
use TYPO3\CMS\Frontend\Typolink\UnableToLinkException;
77
78
/**
79
 * This class contains all main TypoScript features.
80
 * This includes the rendering of TypoScript content objects (cObjects).
81
 * Is the backbone of TypoScript Template rendering.
82
 *
83
 * There are lots of functions you can use from your include-scripts.
84
 * The class is normally instantiated and referred to as "cObj".
85
 * 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.
86
 */
87
class ContentObjectRenderer implements LoggerAwareInterface
88
{
89
    use LoggerAwareTrait;
90
91
    /**
92
     * @var ContainerInterface
93
     */
94
    protected $container;
95
96
    /**
97
     * @var array
98
     */
99
    public $align = [
100
        'center',
101
        'right',
102
        'left'
103
    ];
104
105
    /**
106
     * stdWrap functions in their correct order
107
     *
108
     * @see stdWrap()
109
     * @var string[]
110
     */
111
    public $stdWrapOrder = [
112
        'stdWrapPreProcess' => 'hook',
113
        // this is a placeholder for the first Hook
114
        'cacheRead' => 'hook',
115
        // this is a placeholder for checking if the content is available in cache
116
        'setContentToCurrent' => 'boolean',
117
        'setContentToCurrent.' => 'array',
118
        'addPageCacheTags' => 'string',
119
        'addPageCacheTags.' => 'array',
120
        'setCurrent' => 'string',
121
        'setCurrent.' => 'array',
122
        'lang.' => 'array',
123
        'data' => 'getText',
124
        'data.' => 'array',
125
        'field' => 'fieldName',
126
        'field.' => 'array',
127
        'current' => 'boolean',
128
        'current.' => 'array',
129
        'cObject' => 'cObject',
130
        'cObject.' => 'array',
131
        'numRows.' => 'array',
132
        'preUserFunc' => 'functionName',
133
        'stdWrapOverride' => 'hook',
134
        // this is a placeholder for the second Hook
135
        'override' => 'string',
136
        'override.' => 'array',
137
        'preIfEmptyListNum' => 'listNum',
138
        'preIfEmptyListNum.' => 'array',
139
        'ifNull' => 'string',
140
        'ifNull.' => 'array',
141
        'ifEmpty' => 'string',
142
        'ifEmpty.' => 'array',
143
        'ifBlank' => 'string',
144
        'ifBlank.' => 'array',
145
        'listNum' => 'listNum',
146
        'listNum.' => 'array',
147
        'trim' => 'boolean',
148
        'trim.' => 'array',
149
        'strPad.' => 'array',
150
        'stdWrap' => 'stdWrap',
151
        'stdWrap.' => 'array',
152
        'stdWrapProcess' => 'hook',
153
        // this is a placeholder for the third Hook
154
        'required' => 'boolean',
155
        'required.' => 'array',
156
        'if.' => 'array',
157
        'fieldRequired' => 'fieldName',
158
        'fieldRequired.' => 'array',
159
        'csConv' => 'string',
160
        'csConv.' => 'array',
161
        'parseFunc' => 'objectpath',
162
        'parseFunc.' => 'array',
163
        'HTMLparser' => 'boolean',
164
        'HTMLparser.' => 'array',
165
        'split.' => 'array',
166
        'replacement.' => 'array',
167
        'prioriCalc' => 'boolean',
168
        'prioriCalc.' => 'array',
169
        'char' => 'integer',
170
        'char.' => 'array',
171
        'intval' => 'boolean',
172
        'intval.' => 'array',
173
        'hash' => 'string',
174
        'hash.' => 'array',
175
        'round' => 'boolean',
176
        'round.' => 'array',
177
        'numberFormat.' => 'array',
178
        'expandList' => 'boolean',
179
        'expandList.' => 'array',
180
        'date' => 'dateconf',
181
        'date.' => 'array',
182
        'strtotime' => 'strtotimeconf',
183
        'strtotime.' => 'array',
184
        'strftime' => 'strftimeconf',
185
        'strftime.' => 'array',
186
        'age' => 'boolean',
187
        'age.' => 'array',
188
        'case' => 'case',
189
        'case.' => 'array',
190
        'bytes' => 'boolean',
191
        'bytes.' => 'array',
192
        'substring' => 'parameters',
193
        'substring.' => 'array',
194
        'cropHTML' => 'crop',
195
        'cropHTML.' => 'array',
196
        'stripHtml' => 'boolean',
197
        'stripHtml.' => 'array',
198
        'crop' => 'crop',
199
        'crop.' => 'array',
200
        'rawUrlEncode' => 'boolean',
201
        'rawUrlEncode.' => 'array',
202
        'htmlSpecialChars' => 'boolean',
203
        'htmlSpecialChars.' => 'array',
204
        'encodeForJavaScriptValue' => 'boolean',
205
        'encodeForJavaScriptValue.' => 'array',
206
        'doubleBrTag' => 'string',
207
        'doubleBrTag.' => 'array',
208
        'br' => 'boolean',
209
        'br.' => 'array',
210
        'brTag' => 'string',
211
        'brTag.' => 'array',
212
        'encapsLines.' => 'array',
213
        'keywords' => 'boolean',
214
        'keywords.' => 'array',
215
        'innerWrap' => 'wrap',
216
        'innerWrap.' => 'array',
217
        'innerWrap2' => 'wrap',
218
        'innerWrap2.' => 'array',
219
        'preCObject' => 'cObject',
220
        'preCObject.' => 'array',
221
        'postCObject' => 'cObject',
222
        'postCObject.' => 'array',
223
        'wrapAlign' => 'align',
224
        'wrapAlign.' => 'array',
225
        'typolink.' => 'array',
226
        'wrap' => 'wrap',
227
        'wrap.' => 'array',
228
        'noTrimWrap' => 'wrap',
229
        'noTrimWrap.' => 'array',
230
        'wrap2' => 'wrap',
231
        'wrap2.' => 'array',
232
        'dataWrap' => 'dataWrap',
233
        'dataWrap.' => 'array',
234
        'prepend' => 'cObject',
235
        'prepend.' => 'array',
236
        'append' => 'cObject',
237
        'append.' => 'array',
238
        'wrap3' => 'wrap',
239
        'wrap3.' => 'array',
240
        'orderedStdWrap' => 'stdWrap',
241
        'orderedStdWrap.' => 'array',
242
        'outerWrap' => 'wrap',
243
        'outerWrap.' => 'array',
244
        'insertData' => 'boolean',
245
        'insertData.' => 'array',
246
        'postUserFunc' => 'functionName',
247
        'postUserFuncInt' => 'functionName',
248
        'prefixComment' => 'string',
249
        'prefixComment.' => 'array',
250
        'editIcons' => 'string',
251
        'editIcons.' => 'array',
252
        'editPanel' => 'boolean',
253
        'editPanel.' => 'array',
254
        'cacheStore' => 'hook',
255
        // this is a placeholder for storing the content in cache
256
        'stdWrapPostProcess' => 'hook',
257
        // this is a placeholder for the last Hook
258
        'debug' => 'boolean',
259
        'debug.' => 'array',
260
        'debugFunc' => 'boolean',
261
        'debugFunc.' => 'array',
262
        'debugData' => 'boolean',
263
        'debugData.' => 'array'
264
    ];
265
266
    /**
267
     * Class names for accordant content object names
268
     *
269
     * @var array
270
     */
271
    protected $contentObjectClassMap = [];
272
273
    /**
274
     * Loaded with the current data-record.
275
     *
276
     * If the instance of this class is used to render records from the database those records are found in this array.
277
     * The function stdWrap has TypoScript properties that fetch field-data from this array.
278
     *
279
     * @var array
280
     * @see start()
281
     */
282
    public $data = [];
283
284
    /**
285
     * @var string
286
     */
287
    protected $table = '';
288
289
    /**
290
     * Used for backup
291
     *
292
     * @var array
293
     */
294
    public $oldData = [];
295
296
    /**
297
     * If this is set with an array before stdWrap, it's used instead of $this->data in the data-property in stdWrap
298
     *
299
     * @var string
300
     */
301
    public $alternativeData = '';
302
303
    /**
304
     * Used by the parseFunc function and is loaded with tag-parameters when parsing tags.
305
     *
306
     * @var array
307
     */
308
    public $parameters = [];
309
310
    /**
311
     * @var string
312
     */
313
    public $currentValKey = 'currentValue_kidjls9dksoje';
314
315
    /**
316
     * This is set to the [table]:[uid] of the record delivered in the $data-array, if the cObjects CONTENT or RECORD is in operation.
317
     * Note that $GLOBALS['TSFE']->currentRecord is set to an equal value but always indicating the latest record rendered.
318
     *
319
     * @var string
320
     */
321
    public $currentRecord = '';
322
323
    /**
324
     * Set in RecordsContentObject and ContentContentObject to the current number of records selected in a query.
325
     *
326
     * @var int
327
     */
328
    public $currentRecordTotal = 0;
329
330
    /**
331
     * Incremented in RecordsContentObject and ContentContentObject before each record rendering.
332
     *
333
     * @var int
334
     */
335
    public $currentRecordNumber = 0;
336
337
    /**
338
     * Incremented in RecordsContentObject and ContentContentObject before each record rendering.
339
     *
340
     * @var int
341
     */
342
    public $parentRecordNumber = 0;
343
344
    /**
345
     * 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.
346
     *
347
     * @var array
348
     */
349
    public $parentRecord = [];
350
351
    /**
352
     * @var string|int
353
     */
354
    public $checkPid_badDoktypeList = PageRepository::DOKTYPE_RECYCLER;
355
356
    /**
357
     * This will be set by typoLink() to the url of the most recent link created.
358
     *
359
     * @var string
360
     */
361
    public $lastTypoLinkUrl = '';
362
363
    /**
364
     * DO. link target.
365
     *
366
     * @var string
367
     */
368
    public $lastTypoLinkTarget = '';
369
370
    /**
371
     * @var array
372
     */
373
    public $lastTypoLinkLD = [];
374
375
    /**
376
     * array that registers rendered content elements (or any table) to make sure they are not rendered recursively!
377
     *
378
     * @var array
379
     */
380
    public $recordRegister = [];
381
382
    /**
383
     * Containing hook objects for stdWrap
384
     *
385
     * @var array
386
     */
387
    protected $stdWrapHookObjects = [];
388
389
    /**
390
     * Containing hook objects for getImgResource
391
     *
392
     * @var array
393
     */
394
    protected $getImgResourceHookObjects;
395
396
    /**
397
     * @var File|FileReference|Folder|null Current file objects (during iterations over files)
0 ignored issues
show
Bug introduced by
The type TYPO3\CMS\Frontend\ContentObject\Folder was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
398
     */
399
    protected $currentFile;
400
401
    /**
402
     * Set to TRUE by doConvertToUserIntObject() if USER object wants to become USER_INT
403
     * @var bool
404
     */
405
    public $doConvertToUserIntObject = false;
406
407
    /**
408
     * Indicates current object type. Can hold one of OBJECTTYPE_ constants or FALSE.
409
     * The value is set and reset inside USER() function. Any time outside of
410
     * USER() it is FALSE.
411
     * @var bool
412
     */
413
    protected $userObjectType = false;
414
415
    /**
416
     * @var array
417
     */
418
    protected $stopRendering = [];
419
420
    /**
421
     * @var int
422
     */
423
    protected $stdWrapRecursionLevel = 0;
424
425
    /**
426
     * @var TypoScriptFrontendController|null
427
     */
428
    protected $typoScriptFrontendController;
429
430
    /**
431
     * Indicates that object type is USER.
432
     *
433
     * @see ContentObjectRender::$userObjectType
434
     */
435
    const OBJECTTYPE_USER_INT = 1;
436
    /**
437
     * Indicates that object type is USER.
438
     *
439
     * @see ContentObjectRender::$userObjectType
440
     */
441
    const OBJECTTYPE_USER = 2;
442
443
    /**
444
     * @param TypoScriptFrontendController $typoScriptFrontendController
445
     * @param ContainerInterface $container
446
     */
447
    public function __construct(TypoScriptFrontendController $typoScriptFrontendController = null, ContainerInterface $container = null)
448
    {
449
        $this->typoScriptFrontendController = $typoScriptFrontendController;
450
        $this->contentObjectClassMap = $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'];
451
        $this->container = $container;
452
    }
453
454
    /**
455
     * Prevent several objects from being serialized.
456
     * If currentFile is set, it is either a File or a FileReference object. As the object itself can't be serialized,
457
     * we have store a hash and restore the object in __wakeup()
458
     *
459
     * @return array
460
     */
461
    public function __sleep()
462
    {
463
        $vars = get_object_vars($this);
464
        unset($vars['typoScriptFrontendController'], $vars['logger'], $vars['container']);
465
        if ($this->currentFile instanceof FileReference) {
466
            $this->currentFile = 'FileReference:' . $this->currentFile->getUid();
467
        } elseif ($this->currentFile instanceof File) {
468
            $this->currentFile = 'File:' . $this->currentFile->getIdentifier();
469
        } else {
470
            unset($vars['currentFile']);
471
        }
472
        return array_keys($vars);
473
    }
474
475
    /**
476
     * Restore currentFile from hash.
477
     * If currentFile references a File, the identifier equals file identifier.
478
     * If it references a FileReference the identifier equals the uid of the reference.
479
     */
480
    public function __wakeup()
481
    {
482
        if (isset($GLOBALS['TSFE'])) {
483
            $this->typoScriptFrontendController = $GLOBALS['TSFE'];
484
        }
485
        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...
486
            [$objectType, $identifier] = explode(':', $this->currentFile, 2);
487
            try {
488
                if ($objectType === 'File') {
489
                    $this->currentFile = GeneralUtility::makeInstance(ResourceFactory::class)->retrieveFileOrFolderObject($identifier);
490
                } elseif ($objectType === 'FileReference') {
491
                    $this->currentFile = GeneralUtility::makeInstance(ResourceFactory::class)->getFileReferenceObject($identifier);
492
                }
493
            } catch (ResourceDoesNotExistException $e) {
494
                $this->currentFile = null;
495
            }
496
        }
497
        $this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
498
        $this->container = GeneralUtility::getContainer();
499
    }
500
501
    /**
502
     * Allow injecting content object class map.
503
     *
504
     * This method is private API, please use configuration
505
     * $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'] to add new content objects
506
     *
507
     * @internal
508
     * @param array $contentObjectClassMap
509
     */
510
    public function setContentObjectClassMap(array $contentObjectClassMap)
511
    {
512
        $this->contentObjectClassMap = $contentObjectClassMap;
513
    }
514
515
    /**
516
     * Register a single content object name to class name
517
     *
518
     * This method is private API, please use configuration
519
     * $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'] to add new content objects
520
     *
521
     * @param string $className
522
     * @param string $contentObjectName
523
     * @internal
524
     */
525
    public function registerContentObjectClass($className, $contentObjectName)
526
    {
527
        $this->contentObjectClassMap[$contentObjectName] = $className;
528
    }
529
530
    /**
531
     * Class constructor.
532
     * Well, it has to be called manually since it is not a real constructor function.
533
     * 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.
534
     *
535
     * @param array $data The record data that is rendered.
536
     * @param string $table The table that the data record is from.
537
     */
538
    public function start($data, $table = '')
539
    {
540
        $this->data = $data;
541
        $this->table = $table;
542
        $this->currentRecord = $table !== ''
543
            ? $table . ':' . ($this->data['uid'] ?? '')
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
544
            : '';
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
545
        $this->parameters = [];
546
        $this->stdWrapHookObjects = [];
547
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap'] ?? [] as $className) {
548
            $hookObject = GeneralUtility::makeInstance($className);
549
            if (!$hookObject instanceof ContentObjectStdWrapHookInterface) {
550
                throw new \UnexpectedValueException($className . ' must implement interface ' . ContentObjectStdWrapHookInterface::class, 1195043965);
551
            }
552
            $this->stdWrapHookObjects[] = $hookObject;
553
        }
554
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['postInit'] ?? [] as $className) {
555
            $postInitializationProcessor = GeneralUtility::makeInstance($className);
556
            if (!$postInitializationProcessor instanceof ContentObjectPostInitHookInterface) {
557
                throw new \UnexpectedValueException($className . ' must implement interface ' . ContentObjectPostInitHookInterface::class, 1274563549);
558
            }
559
            $postInitializationProcessor->postProcessContentObjectInitialization($this);
560
        }
561
    }
562
563
    /**
564
     * Returns the current table
565
     *
566
     * @return string
567
     */
568
    public function getCurrentTable()
569
    {
570
        return $this->table;
571
    }
572
573
    /**
574
     * Gets the 'getImgResource' hook objects.
575
     * The first call initializes the accordant objects.
576
     *
577
     * @return array The 'getImgResource' hook objects (if any)
578
     */
579
    protected function getGetImgResourceHookObjects()
580
    {
581
        if (!isset($this->getImgResourceHookObjects)) {
582
            $this->getImgResourceHookObjects = [];
583
            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImgResource'] ?? [] as $className) {
584
                $hookObject = GeneralUtility::makeInstance($className);
585
                if (!$hookObject instanceof ContentObjectGetImageResourceHookInterface) {
586
                    throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetImageResourceHookInterface::class, 1218636383);
587
                }
588
                $this->getImgResourceHookObjects[] = $hookObject;
589
            }
590
        }
591
        return $this->getImgResourceHookObjects;
592
    }
593
594
    /**
595
     * Sets the internal variable parentRecord with information about current record.
596
     * 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.
597
     *
598
     * @param array $data The record array
599
     * @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.
600
     * @internal
601
     */
602
    public function setParent($data, $currentRecord)
603
    {
604
        $this->parentRecord = [
605
            'data' => $data,
606
            'currentRecord' => $currentRecord
607
        ];
608
    }
609
610
    /***********************************************
611
     *
612
     * CONTENT_OBJ:
613
     *
614
     ***********************************************/
615
    /**
616
     * Returns the "current" value.
617
     * 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.
618
     * It's like "load accumulator" in the good old C64 days... basically a "register" you can use as you like.
619
     * 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.
620
     *
621
     * @return mixed The "current" value
622
     */
623
    public function getCurrentVal()
624
    {
625
        return $this->data[$this->currentValKey];
626
    }
627
628
    /**
629
     * Sets the "current" value.
630
     *
631
     * @param mixed $value The variable that you want to set as "current
632
     * @see getCurrentVal()
633
     */
634
    public function setCurrentVal($value)
635
    {
636
        $this->data[$this->currentValKey] = $value;
637
    }
638
639
    /**
640
     * Rendering of a "numerical array" of cObjects from TypoScript
641
     * Will call ->cObjGetSingle() for each cObject found and accumulate the output.
642
     *
643
     * @param array $setup array with cObjects as values.
644
     * @param string $addKey A prefix for the debugging information
645
     * @return string Rendered output from the cObjects in the array.
646
     * @see cObjGetSingle()
647
     */
648
    public function cObjGet($setup, $addKey = '')
649
    {
650
        if (!is_array($setup)) {
0 ignored issues
show
introduced by
The condition is_array($setup) is always true.
Loading history...
651
            return '';
652
        }
653
        $sKeyArray = ArrayUtility::filterAndSortByNumericKeys($setup);
654
        $content = '';
655
        foreach ($sKeyArray as $theKey) {
656
            $theValue = $setup[$theKey];
657
            if ((int)$theKey && strpos($theKey, '.') === false) {
658
                $conf = $setup[$theKey . '.'];
659
                $content .= $this->cObjGetSingle($theValue, $conf, $addKey . $theKey);
660
            }
661
        }
662
        return $content;
663
    }
664
665
    /**
666
     * Renders a content object
667
     *
668
     * @param string $name The content object name, eg. "TEXT" or "USER" or "IMAGE"
669
     * @param array $conf The array with TypoScript properties for the content object
670
     * @param string $TSkey A string label used for the internal debugging tracking.
671
     * @return string cObject output
672
     * @throws \UnexpectedValueException
673
     */
674
    public function cObjGetSingle($name, $conf, $TSkey = '__')
675
    {
676
        $content = '';
677
        // Checking that the function is not called eternally. This is done by interrupting at a depth of 100
678
        $this->getTypoScriptFrontendController()->cObjectDepthCounter--;
679
        if ($this->getTypoScriptFrontendController()->cObjectDepthCounter > 0) {
680
            $timeTracker = $this->getTimeTracker();
681
            $name = trim($name);
682
            if ($timeTracker->LR) {
683
                $timeTracker->push($TSkey, $name);
684
            }
685
            // Checking if the COBJ is a reference to another object. (eg. name of 'some.object =< styles.something')
686
            if (isset($name[0]) && $name[0] === '<') {
687
                $key = trim(substr($name, 1));
688
                $cF = GeneralUtility::makeInstance(TypoScriptParser::class);
689
                // $name and $conf is loaded with the referenced values.
690
                $confOverride = is_array($conf) ? $conf : [];
0 ignored issues
show
introduced by
The condition is_array($conf) is always true.
Loading history...
691
                [$name, $conf] = $cF->getVal($key, $this->getTypoScriptFrontendController()->tmpl->setup);
692
                $conf = array_replace_recursive(is_array($conf) ? $conf : [], $confOverride);
693
                // Getting the cObject
694
                $timeTracker->incStackPointer();
695
                $content .= $this->cObjGetSingle($name, $conf, $key);
696
                $timeTracker->decStackPointer();
697
            } else {
698
                $contentObject = $this->getContentObject($name);
699
                if ($contentObject) {
700
                    $content .= $this->render($contentObject, $conf);
701
                }
702
            }
703
            if ($timeTracker->LR) {
704
                $timeTracker->pull($content);
705
            }
706
        }
707
        // Increasing on exit...
708
        $this->getTypoScriptFrontendController()->cObjectDepthCounter++;
709
        return $content;
710
    }
711
712
    /**
713
     * Returns a new content object of type $name.
714
     * This content object needs to be registered as content object
715
     * in $this->contentObjectClassMap
716
     *
717
     * @param string $name
718
     * @return AbstractContentObject|null
719
     * @throws ContentRenderingException
720
     */
721
    public function getContentObject($name)
722
    {
723
        if (!isset($this->contentObjectClassMap[$name])) {
724
            return null;
725
        }
726
        $fullyQualifiedClassName = $this->contentObjectClassMap[$name];
727
        $contentObject = GeneralUtility::makeInstance($fullyQualifiedClassName, $this);
728
        if (!($contentObject instanceof AbstractContentObject)) {
729
            throw new ContentRenderingException(sprintf('Registered content object class name "%s" must be an instance of AbstractContentObject, but is not!', $fullyQualifiedClassName), 1422564295);
730
        }
731
        return $contentObject;
732
    }
733
734
    /********************************************
735
     *
736
     * Functions rendering content objects (cObjects)
737
     *
738
     ********************************************/
739
    /**
740
     * Renders a content object by taking exception and cache handling
741
     * into consideration
742
     *
743
     * @param AbstractContentObject $contentObject Content object instance
744
     * @param array $configuration Array of TypoScript properties
745
     *
746
     * @throws ContentRenderingException
747
     * @throws \Exception
748
     * @return string
749
     */
750
    public function render(AbstractContentObject $contentObject, $configuration = [])
751
    {
752
        $content = '';
753
754
        // Evaluate possible cache and return
755
        $cacheConfiguration = $configuration['cache.'] ?? null;
756
        if ($cacheConfiguration !== null) {
757
            unset($configuration['cache.']);
758
            $cache = $this->getFromCache($cacheConfiguration);
759
            if ($cache !== false) {
760
                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...
761
            }
762
        }
763
764
        // Render content
765
        try {
766
            $content .= $contentObject->render($configuration);
767
        } catch (ContentRenderingException $exception) {
768
            // Content rendering Exceptions indicate a critical problem which should not be
769
            // caught e.g. when something went wrong with Exception handling itself
770
            throw $exception;
771
        } catch (\Exception $exception) {
772
            $exceptionHandler = $this->createExceptionHandler($configuration);
773
            if ($exceptionHandler === null) {
774
                throw $exception;
775
            }
776
            $content = $exceptionHandler->handle($exception, $contentObject, $configuration);
777
        }
778
779
        // Store cache
780
        if ($cacheConfiguration !== null && !$this->getTypoScriptFrontendController()->no_cache) {
781
            $key = $this->calculateCacheKey($cacheConfiguration);
782
            if (!empty($key)) {
783
                /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cacheFrontend */
784
                $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)->getCache('hash');
785
                $tags = $this->calculateCacheTags($cacheConfiguration);
786
                $lifetime = $this->calculateCacheLifetime($cacheConfiguration);
787
                $cacheFrontend->set($key, $content, $tags, $lifetime);
788
            }
789
        }
790
791
        return $content;
792
    }
793
794
    /**
795
     * Creates the content object exception handler from local content object configuration
796
     * or, from global configuration if not explicitly disabled in local configuration
797
     *
798
     * @param array $configuration
799
     * @return ExceptionHandlerInterface|null
800
     * @throws ContentRenderingException
801
     */
802
    protected function createExceptionHandler($configuration = [])
803
    {
804
        $exceptionHandler = null;
805
        $exceptionHandlerClassName = $this->determineExceptionHandlerClassName($configuration);
806
        if (!empty($exceptionHandlerClassName)) {
807
            $exceptionHandler = GeneralUtility::makeInstance($exceptionHandlerClassName, $this->mergeExceptionHandlerConfiguration($configuration));
808
            if (!$exceptionHandler instanceof ExceptionHandlerInterface) {
809
                throw new ContentRenderingException('An exception handler was configured but the class does not exist or does not implement the ExceptionHandlerInterface', 1403653369);
810
            }
811
        }
812
813
        return $exceptionHandler;
814
    }
815
816
    /**
817
     * Determine exception handler class name from global and content object configuration
818
     *
819
     * @param array $configuration
820
     * @return string|null
821
     */
822
    protected function determineExceptionHandlerClassName($configuration)
823
    {
824
        $exceptionHandlerClassName = null;
825
        $tsfe = $this->getTypoScriptFrontendController();
826
        if (!isset($tsfe->config['config']['contentObjectExceptionHandler'])) {
827
            if (Environment::getContext()->isProduction()) {
828
                $exceptionHandlerClassName = '1';
829
            }
830
        } else {
831
            $exceptionHandlerClassName = $tsfe->config['config']['contentObjectExceptionHandler'];
832
        }
833
834
        if (isset($configuration['exceptionHandler'])) {
835
            $exceptionHandlerClassName = $configuration['exceptionHandler'];
836
        }
837
838
        if ($exceptionHandlerClassName === '1') {
839
            $exceptionHandlerClassName = ProductionExceptionHandler::class;
840
        }
841
842
        return $exceptionHandlerClassName;
843
    }
844
845
    /**
846
     * Merges global exception handler configuration with the one from the content object
847
     * and returns the merged exception handler configuration
848
     *
849
     * @param array $configuration
850
     * @return array
851
     */
852
    protected function mergeExceptionHandlerConfiguration($configuration)
853
    {
854
        $exceptionHandlerConfiguration = [];
855
        $tsfe = $this->getTypoScriptFrontendController();
856
        if (!empty($tsfe->config['config']['contentObjectExceptionHandler.'])) {
857
            $exceptionHandlerConfiguration = $tsfe->config['config']['contentObjectExceptionHandler.'];
858
        }
859
        if (!empty($configuration['exceptionHandler.'])) {
860
            $exceptionHandlerConfiguration = array_replace_recursive($exceptionHandlerConfiguration, $configuration['exceptionHandler.']);
861
        }
862
863
        return $exceptionHandlerConfiguration;
864
    }
865
866
    /**
867
     * Retrieves a type of object called as USER or USER_INT. Object can detect their
868
     * type by using this call. It returns OBJECTTYPE_USER_INT or OBJECTTYPE_USER depending on the
869
     * current object execution. In all other cases it will return FALSE to indicate
870
     * a call out of context.
871
     *
872
     * @return mixed One of OBJECTTYPE_ class constants or FALSE
873
     */
874
    public function getUserObjectType()
875
    {
876
        return $this->userObjectType;
877
    }
878
879
    /**
880
     * Sets the user object type
881
     *
882
     * @param mixed $userObjectType
883
     */
884
    public function setUserObjectType($userObjectType)
885
    {
886
        $this->userObjectType = $userObjectType;
887
    }
888
889
    /**
890
     * Requests the current USER object to be converted to USER_INT.
891
     */
892
    public function convertToUserIntObject()
893
    {
894
        if ($this->userObjectType !== self::OBJECTTYPE_USER) {
0 ignored issues
show
introduced by
The condition $this->userObjectType !== self::OBJECTTYPE_USER is always true.
Loading history...
895
            $this->getTimeTracker()->setTSlogMessage(self::class . '::convertToUserIntObject() is called in the wrong context or for the wrong object type', 2);
896
        } else {
897
            $this->doConvertToUserIntObject = true;
898
        }
899
    }
900
901
    /************************************
902
     *
903
     * Various helper functions for content objects:
904
     *
905
     ************************************/
906
    /**
907
     * Converts a given config in Flexform to a conf-array
908
     *
909
     * @param string|array $flexData Flexform data
910
     * @param array $conf Array to write the data into, by reference
911
     * @param bool $recursive Is set if called recursive. Don't call function with this parameter, it's used inside the function only
912
     */
913
    public function readFlexformIntoConf($flexData, &$conf, $recursive = false)
914
    {
915
        if ($recursive === false && is_string($flexData)) {
916
            $flexData = GeneralUtility::xml2array($flexData, 'T3');
917
        }
918
        if (is_array($flexData) && isset($flexData['data']['sDEF']['lDEF'])) {
919
            $flexData = $flexData['data']['sDEF']['lDEF'];
920
        }
921
        if (!is_array($flexData)) {
922
            return;
923
        }
924
        foreach ($flexData as $key => $value) {
925
            if (!is_array($value)) {
926
                continue;
927
            }
928
            if (isset($value['el'])) {
929
                if (is_array($value['el']) && !empty($value['el'])) {
930
                    foreach ($value['el'] as $ekey => $element) {
931
                        if (isset($element['vDEF'])) {
932
                            $conf[$ekey] = $element['vDEF'];
933
                        } else {
934
                            if (is_array($element)) {
935
                                $this->readFlexformIntoConf($element, $conf[$key][key($element)][$ekey], true);
936
                            } else {
937
                                $this->readFlexformIntoConf($element, $conf[$key][$ekey], true);
938
                            }
939
                        }
940
                    }
941
                } else {
942
                    $this->readFlexformIntoConf($value['el'], $conf[$key], true);
943
                }
944
            }
945
            if (isset($value['vDEF'])) {
946
                $conf[$key] = $value['vDEF'];
947
            }
948
        }
949
    }
950
951
    /**
952
     * Returns all parents of the given PID (Page UID) list
953
     *
954
     * @param string $pidList A list of page Content-Element PIDs (Page UIDs) / stdWrap
955
     * @param array $pidConf stdWrap array for the list
956
     * @return string A list of PIDs
957
     * @internal
958
     */
959
    public function getSlidePids($pidList, $pidConf)
960
    {
961
        // todo: phpstan states that $pidConf always exists and is not nullable. At the moment, this is a false positive
962
        //       as null can be passed into this method via $pidConf. As soon as more strict types are used, this isset
963
        //       check must be replaced with a more appropriate check like empty or count.
964
        $pidList = isset($pidConf) ? trim($this->stdWrap($pidList, $pidConf)) : trim($pidList);
965
        if ($pidList === '') {
966
            $pidList = 'this';
967
        }
968
        $tsfe = $this->getTypoScriptFrontendController();
969
        $listArr = null;
970
        if (trim($pidList)) {
971
            $listArr = GeneralUtility::intExplode(',', str_replace('this', $tsfe->contentPid, $pidList));
972
            $listArr = $this->checkPidArray($listArr);
973
        }
974
        $pidList = [];
975
        if (is_array($listArr) && !empty($listArr)) {
976
            foreach ($listArr as $uid) {
977
                $page = $tsfe->sys_page->getPage($uid);
978
                if (!$page['is_siteroot']) {
979
                    $pidList[] = $page['pid'];
980
                }
981
            }
982
        }
983
        return implode(',', $pidList);
984
    }
985
986
    /**
987
     * Wraps the input string in link-tags that opens the image in a new window.
988
     *
989
     * @param string $string String to wrap, probably an <img> tag
990
     * @param string|File|FileReference $imageFile The original image file
991
     * @param array $conf TypoScript properties for the "imageLinkWrap" function
992
     * @return string The input string, $string, wrapped as configured.
993
     * @internal This method should be used within TYPO3 Core only
994
     */
995
    public function imageLinkWrap($string, $imageFile, $conf)
996
    {
997
        $string = (string)$string;
998
        $enable = $this->stdWrapValue('enable', $conf);
999
        if (!$enable) {
1000
            return $string;
1001
        }
1002
        $content = (string)$this->typoLink($string, $conf['typolink.']);
1003
        if (isset($conf['file.'])) {
1004
            $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

1004
            $imageFile = $this->stdWrap(/** @scrutinizer ignore-type */ $imageFile, $conf['file.']);
Loading history...
1005
        }
1006
1007
        if ($imageFile instanceof File) {
1008
            $file = $imageFile;
1009
        } elseif ($imageFile instanceof FileReference) {
1010
            $file = $imageFile->getOriginalFile();
1011
        } else {
1012
            if (MathUtility::canBeInterpretedAsInteger($imageFile)) {
1013
                $file = GeneralUtility::makeInstance(ResourceFactory::class)->getFileObject((int)$imageFile);
1014
            } else {
1015
                $file = GeneralUtility::makeInstance(ResourceFactory::class)->getFileObjectFromCombinedIdentifier($imageFile);
1016
            }
1017
        }
1018
1019
        // Create imageFileLink if not created with typolink
1020
        if ($content === $string && $file !== null) {
1021
            $parameterNames = ['width', 'height', 'effects', 'bodyTag', 'title', 'wrap', 'crop'];
1022
            $parameters = [];
1023
            $sample = $this->stdWrapValue('sample', $conf);
1024
            if ($sample) {
1025
                $parameters['sample'] = 1;
1026
            }
1027
            foreach ($parameterNames as $parameterName) {
1028
                if (isset($conf[$parameterName . '.'])) {
1029
                    $conf[$parameterName] = $this->stdWrap($conf[$parameterName], $conf[$parameterName . '.']);
1030
                }
1031
                if (isset($conf[$parameterName]) && $conf[$parameterName]) {
1032
                    $parameters[$parameterName] = $conf[$parameterName];
1033
                }
1034
            }
1035
            $parametersEncoded = base64_encode(json_encode($parameters));
1036
            $hmac = GeneralUtility::hmac(implode('|', [$file->getUid(), $parametersEncoded]));
1037
            $params = '&md5=' . $hmac;
1038
            foreach (str_split($parametersEncoded, 64) as $index => $chunk) {
1039
                $params .= '&parameters' . rawurlencode('[') . $index . rawurlencode(']') . '=' . rawurlencode($chunk);
1040
            }
1041
            $url = $this->getTypoScriptFrontendController()->absRefPrefix . 'index.php?eID=tx_cms_showpic&file=' . $file->getUid() . $params;
1042
            $directImageLink = $this->stdWrapValue('directImageLink', $conf);
1043
            if ($directImageLink) {
1044
                $imgResourceConf = [
1045
                    'file' => $imageFile,
1046
                    'file.' => $conf
1047
                ];
1048
                $url = $this->cObjGetSingle('IMG_RESOURCE', $imgResourceConf);
1049
                if (!$url) {
1050
                    // If no imagemagick / gm is available
1051
                    $url = $imageFile;
1052
                }
1053
            }
1054
            // Create TARGET-attribute only if the right doctype is used
1055
            $target = '';
1056
            $xhtmlDocType = $this->getTypoScriptFrontendController()->xhtmlDoctype;
1057
            if ($xhtmlDocType !== 'xhtml_strict' && $xhtmlDocType !== 'xhtml_11') {
1058
                $target = (string)$this->stdWrapValue('target', $conf);
1059
                if ($target === '') {
1060
                    $target = 'thePicture';
1061
                }
1062
            }
1063
            $a1 = '';
1064
            $a2 = '';
1065
            $conf['JSwindow'] = $this->stdWrapValue('JSwindow', $conf);
1066
            if ($conf['JSwindow']) {
1067
                $altUrl = $this->stdWrapValue('altUrl', $conf['JSwindow.'] ?? []);
1068
                if ($altUrl) {
1069
                    $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

1069
                    $url = $altUrl . ($conf['JSwindow.']['altUrl_noDefaultParams'] ? '' : '?file=' . rawurlencode(/** @scrutinizer ignore-type */ $imageFile) . $params);
Loading history...
1070
                }
1071
1072
                $processedFile = $file->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $conf);
1073
                $JSwindowExpand = $this->stdWrapValue('expand', $conf['JSwindow.'] ?? []);
1074
                $offset = GeneralUtility::intExplode(',', $JSwindowExpand . ',');
1075
                $newWindow = $this->stdWrapValue('newWindow', $conf['JSwindow.'] ?? []);
1076
                $params = [
1077
                    'width' => ($processedFile->getProperty('width') + $offset[0]),
1078
                    'height' => ($processedFile->getProperty('height') + $offset[1]),
1079
                    'status' => '0',
1080
                    'menubar' => '0'
1081
                ];
1082
                // params override existing parameters from above, or add more
1083
                $windowParams = $this->stdWrapValue('params', $conf['JSwindow.'] ?? []);
1084
                $windowParams = explode(',', $windowParams);
1085
                foreach ($windowParams as $windowParam) {
1086
                    [$paramKey, $paramValue] = explode('=', $windowParam);
1087
                    if ($paramValue !== '') {
1088
                        $params[$paramKey] = $paramValue;
1089
                    } else {
1090
                        unset($params[$paramKey]);
1091
                    }
1092
                }
1093
                $paramString = '';
1094
                foreach ($params as $paramKey => $paramValue) {
1095
                    $paramString .= htmlspecialchars($paramKey) . '=' . htmlspecialchars($paramValue) . ',';
1096
                }
1097
1098
                $onClick = 'openPic('
1099
                    . 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

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

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

1102
                $a1 = '<a href="' . htmlspecialchars(/** @scrutinizer ignore-type */ $url) . '"'
Loading history...
1103
                    . ' onclick="' . htmlspecialchars($onClick) . '"'
1104
                    . ($target !== '' ? ' target="' . htmlspecialchars($target) . '"' : '')
1105
                    . $this->getTypoScriptFrontendController()->ATagParams . '>';
1106
                $a2 = '</a>';
1107
                GeneralUtility::makeInstance(AssetCollector::class)->addInlineJavaScript('openPic', 'function openPic(url, winName, winParams) { var theWindow = window.open(url, winName, winParams); if (theWindow) { theWindow.focus(); } }');
1108
            } else {
1109
                $conf['linkParams.']['directImageLink'] = (bool)$conf['directImageLink'];
1110
                $conf['linkParams.']['parameter'] = $url;
1111
                $string = $this->typoLink($string, $conf['linkParams.']);
1112
            }
1113
            if (isset($conf['stdWrap.'])) {
1114
                $string = $this->stdWrap($string, $conf['stdWrap.']);
1115
            }
1116
            $content = $a1 . $string . $a2;
1117
        }
1118
        return $content;
1119
    }
1120
1121
    /**
1122
     * Sets the SYS_LASTCHANGED timestamp if input timestamp is larger than current value.
1123
     * The SYS_LASTCHANGED timestamp can be used by various caching/indexing applications to determine if the page has new content.
1124
     * Therefore you should call this function with the last-changed timestamp of any element you display.
1125
     *
1126
     * @param int $tstamp Unix timestamp (number of seconds since 1970)
1127
     * @see TypoScriptFrontendController::setSysLastChanged()
1128
     */
1129
    public function lastChanged($tstamp)
1130
    {
1131
        $tstamp = (int)$tstamp;
1132
        $tsfe = $this->getTypoScriptFrontendController();
1133
        if ($tstamp > (int)$tsfe->register['SYS_LASTCHANGED']) {
1134
            $tsfe->register['SYS_LASTCHANGED'] = $tstamp;
1135
        }
1136
    }
1137
1138
    /**
1139
     * An abstraction method to add parameters to an A tag.
1140
     * Uses the ATagParams property.
1141
     *
1142
     * @param array $conf TypoScript configuration properties
1143
     * @param bool|int $addGlobal If set, will add the global config.ATagParams to the link
1144
     * @return string String containing the parameters to the A tag (if non empty, with a leading space)
1145
     * @see typolink()
1146
     */
1147
    public function getATagParams($conf, $addGlobal = 1)
1148
    {
1149
        $aTagParams = ' ' . $this->stdWrapValue('ATagParams', $conf);
1150
        if ($addGlobal) {
1151
            $aTagParams = ' ' . trim($this->getTypoScriptFrontendController()->ATagParams . $aTagParams);
1152
        }
1153
        // Extend params
1154
        $_params = [
1155
            'conf' => &$conf,
1156
            'aTagParams' => &$aTagParams
1157
        ];
1158
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getATagParamsPostProc'] ?? [] as $className) {
1159
            $processor = GeneralUtility::makeInstance($className);
1160
            $aTagParams = $processor->process($_params, $this);
1161
        }
1162
1163
        $aTagParams = trim($aTagParams);
1164
        if (!empty($aTagParams)) {
1165
            $aTagParams = ' ' . $aTagParams;
1166
        }
1167
1168
        return $aTagParams;
1169
    }
1170
1171
    /***********************************************
1172
     *
1173
     * HTML template processing functions
1174
     *
1175
     ***********************************************/
1176
1177
    /**
1178
     * Sets the current file object during iterations over files.
1179
     *
1180
     * @param File $fileObject The file object.
1181
     */
1182
    public function setCurrentFile($fileObject)
1183
    {
1184
        $this->currentFile = $fileObject;
1185
    }
1186
1187
    /**
1188
     * Gets the current file object during iterations over files.
1189
     *
1190
     * @return File The current file object.
1191
     */
1192
    public function getCurrentFile()
1193
    {
1194
        return $this->currentFile;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->currentFile also could return the type TYPO3\CMS\Core\Resource\FileReference which is incompatible with the documented return type TYPO3\CMS\Core\Resource\File.
Loading history...
1195
    }
1196
1197
    /***********************************************
1198
     *
1199
     * "stdWrap" + sub functions
1200
     *
1201
     ***********************************************/
1202
    /**
1203
     * The "stdWrap" function. This is the implementation of what is known as "stdWrap properties" in TypoScript.
1204
     * Basically "stdWrap" performs some processing of a value based on properties in the input $conf array(holding the TypoScript "stdWrap properties")
1205
     * 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.
1206
     *
1207
     * If $this->alternativeData is an array it's used instead of the $this->data array in ->getData
1208
     *
1209
     * @param string $content Input value undergoing processing in this function. Possibly substituted by other values fetched from another source.
1210
     * @param array $conf TypoScript "stdWrap properties".
1211
     * @return string The processed input value
1212
     */
1213
    public function stdWrap($content = '', $conf = [])
1214
    {
1215
        $content = (string)$content;
1216
        // If there is any hook object, activate all of the process and override functions.
1217
        // The hook interface ContentObjectStdWrapHookInterface takes care that all 4 methods exist.
1218
        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...
1219
            $conf['stdWrapPreProcess'] = 1;
1220
            $conf['stdWrapOverride'] = 1;
1221
            $conf['stdWrapProcess'] = 1;
1222
            $conf['stdWrapPostProcess'] = 1;
1223
        }
1224
1225
        if (!is_array($conf) || !$conf) {
0 ignored issues
show
introduced by
The condition is_array($conf) is always true.
Loading history...
1226
            return $content;
1227
        }
1228
1229
        // Cache handling
1230
        if (isset($conf['cache.']) && is_array($conf['cache.'])) {
1231
            $conf['cache.']['key'] = $this->stdWrapValue('key', $conf['cache.']);
1232
            $conf['cache.']['tags'] = $this->stdWrapValue('tags', $conf['cache.']);
1233
            $conf['cache.']['lifetime'] = $this->stdWrapValue('lifetime', $conf['cache.']);
1234
            $conf['cacheRead'] = 1;
1235
            $conf['cacheStore'] = 1;
1236
        }
1237
        // The configuration is sorted and filtered by intersection with the defined stdWrapOrder.
1238
        $sortedConf = array_keys(array_intersect_key($this->stdWrapOrder, $conf));
1239
        // Functions types that should not make use of nested stdWrap function calls to avoid conflicts with internal TypoScript used by these functions
1240
        $stdWrapDisabledFunctionTypes = 'cObject,functionName,stdWrap';
1241
        // Additional Array to check whether a function has already been executed
1242
        $isExecuted = [];
1243
        // Additional switch to make sure 'required', 'if' and 'fieldRequired'
1244
        // will still stop rendering immediately in case they return FALSE
1245
        $this->stdWrapRecursionLevel++;
1246
        $this->stopRendering[$this->stdWrapRecursionLevel] = false;
1247
        // execute each function in the predefined order
1248
        foreach ($sortedConf as $stdWrapName) {
1249
            // eliminate the second key of a pair 'key'|'key.' to make sure functions get called only once and check if rendering has been stopped
1250
            if ((!isset($isExecuted[$stdWrapName]) || !$isExecuted[$stdWrapName]) && !$this->stopRendering[$this->stdWrapRecursionLevel]) {
1251
                $functionName = rtrim($stdWrapName, '.');
1252
                $functionProperties = $functionName . '.';
1253
                $functionType = $this->stdWrapOrder[$functionName] ?? null;
1254
                // If there is any code on the next level, check if it contains "official" stdWrap functions
1255
                // if yes, execute them first - will make each function stdWrap aware
1256
                // so additional stdWrap calls within the functions can be removed, since the result will be the same
1257
                if (!empty($conf[$functionProperties]) && !GeneralUtility::inList($stdWrapDisabledFunctionTypes, $functionType)) {
1258
                    if (array_intersect_key($this->stdWrapOrder, $conf[$functionProperties])) {
1259
                        // Check if there's already content available before processing
1260
                        // any ifEmpty or ifBlank stdWrap properties
1261
                        if (($functionName === 'ifBlank' && $content !== '') ||
1262
                            ($functionName === 'ifEmpty' && trim($content) !== '')) {
1263
                            continue;
1264
                        }
1265
1266
                        $conf[$functionName] = $this->stdWrap($conf[$functionName] ?? '', $conf[$functionProperties] ?? []);
1267
                    }
1268
                }
1269
                // Check if key is still containing something, since it might have been changed by next level stdWrap before
1270
                if ((isset($conf[$functionName]) || $conf[$functionProperties])
1271
                    && ($functionType !== 'boolean' || $conf[$functionName])
1272
                ) {
1273
                    // Get just that part of $conf that is needed for the particular function
1274
                    $singleConf = [
1275
                        $functionName => $conf[$functionName] ?? null,
1276
                        $functionProperties => $conf[$functionProperties] ?? null
1277
                    ];
1278
                    // Hand over the whole $conf array to the stdWrapHookObjects
1279
                    if ($functionType === 'hook') {
1280
                        $singleConf = $conf;
1281
                    }
1282
                    // Add both keys - with and without the dot - to the set of executed functions
1283
                    $isExecuted[$functionName] = true;
1284
                    $isExecuted[$functionProperties] = true;
1285
                    // Call the function with the prefix stdWrap_ to make sure nobody can execute functions just by adding their name to the TS Array
1286
                    $functionName = 'stdWrap_' . $functionName;
1287
                    $content = $this->{$functionName}($content, $singleConf);
1288
                } elseif ($functionType === 'boolean' && !$conf[$functionName]) {
1289
                    $isExecuted[$functionName] = true;
1290
                    $isExecuted[$functionProperties] = true;
1291
                }
1292
            }
1293
        }
1294
        unset($this->stopRendering[$this->stdWrapRecursionLevel]);
1295
        $this->stdWrapRecursionLevel--;
1296
1297
        return $content;
1298
    }
1299
1300
    /**
1301
     * Gets a configuration value by passing them through stdWrap first and taking a default value if stdWrap doesn't yield a result.
1302
     *
1303
     * @param string $key The config variable key (from TS array).
1304
     * @param array $config The TypoScript array.
1305
     * @param string|int|null $defaultValue Optional default value.
1306
     * @return string|int|null Value of the config variable
1307
     */
1308
    public function stdWrapValue($key, array $config, $defaultValue = '')
1309
    {
1310
        if (isset($config[$key])) {
1311
            if (!isset($config[$key . '.'])) {
1312
                return $config[$key];
1313
            }
1314
        } elseif (isset($config[$key . '.'])) {
1315
            $config[$key] = '';
1316
        } else {
1317
            return $defaultValue;
1318
        }
1319
        $stdWrapped = $this->stdWrap($config[$key], $config[$key . '.']);
1320
        // The string "0" should be returned.
1321
        return $stdWrapped !== '' ? $stdWrapped : $defaultValue;
1322
    }
1323
1324
    /**
1325
     * stdWrap pre process hook
1326
     * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
1327
     * this hook will execute functions before any other stdWrap function can modify anything
1328
     *
1329
     * @param string $content Input value undergoing processing in these functions.
1330
     * @param array $conf All stdWrap properties, not just the ones for a particular function.
1331
     * @return string The processed input value
1332
     */
1333
    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...
1334
    {
1335
        foreach ($this->stdWrapHookObjects as $hookObject) {
1336
            /** @var ContentObjectStdWrapHookInterface $hookObject */
1337
            $content = $hookObject->stdWrapPreProcess($content, $conf, $this);
1338
        }
1339
        return $content;
1340
    }
1341
1342
    /**
1343
     * Check if content was cached before (depending on the given cache key)
1344
     *
1345
     * @param string $content Input value undergoing processing in these functions.
1346
     * @param array $conf All stdWrap properties, not just the ones for a particular function.
1347
     * @return string The processed input value
1348
     */
1349
    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...
1350
    {
1351
        if (!isset($conf['cache.'])) {
1352
            return $content;
1353
        }
1354
        $result = $this->getFromCache($conf['cache.']);
1355
        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...
1356
    }
1357
1358
    /**
1359
     * Add tags to page cache (comma-separated list)
1360
     *
1361
     * @param string $content Input value undergoing processing in these functions.
1362
     * @param array $conf All stdWrap properties, not just the ones for a particular function.
1363
     * @return string The processed input value
1364
     */
1365
    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...
1366
    {
1367
        $tags = $this->stdWrapValue('addPageCacheTags', $conf);
1368
        if (!empty($tags)) {
1369
            $cacheTags = GeneralUtility::trimExplode(',', $tags, true);
1370
            $this->getTypoScriptFrontendController()->addCacheTags($cacheTags);
1371
        }
1372
        return $content;
1373
    }
1374
1375
    /**
1376
     * setContentToCurrent
1377
     * actually it just does the contrary: Sets the value of 'current' based on current content
1378
     *
1379
     * @param string $content Input value undergoing processing in this function.
1380
     * @return string The processed input value
1381
     */
1382
    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...
1383
    {
1384
        $this->data[$this->currentValKey] = $content;
1385
        return $content;
1386
    }
1387
1388
    /**
1389
     * setCurrent
1390
     * Sets the value of 'current' based on the outcome of stdWrap operations
1391
     *
1392
     * @param string $content Input value undergoing processing in this function.
1393
     * @param array $conf stdWrap properties for setCurrent.
1394
     * @return string The processed input value
1395
     */
1396
    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...
1397
    {
1398
        $this->data[$this->currentValKey] = $conf['setCurrent'] ?? null;
1399
        return $content;
1400
    }
1401
1402
    /**
1403
     * lang
1404
     * Translates content based on the language currently used by the FE
1405
     *
1406
     * @param string $content Input value undergoing processing in this function.
1407
     * @param array $conf stdWrap properties for lang.
1408
     * @return string The processed input value
1409
     */
1410
    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...
1411
    {
1412
        $currentLanguageCode = $this->getTypoScriptFrontendController()->getLanguage()->getTypo3Language();
1413
        if ($currentLanguageCode && isset($conf['lang.'][$currentLanguageCode])) {
1414
            $content = $conf['lang.'][$currentLanguageCode];
1415
        }
1416
        return $content;
1417
    }
1418
1419
    /**
1420
     * data
1421
     * Gets content from different sources based on getText functions, makes use of alternativeData, when set
1422
     *
1423
     * @param string $content Input value undergoing processing in this function.
1424
     * @param array $conf stdWrap properties for data.
1425
     * @return string The processed input value
1426
     */
1427
    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...
1428
    {
1429
        $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...
1430
        // This must be unset directly after
1431
        $this->alternativeData = '';
1432
        return $content;
1433
    }
1434
1435
    /**
1436
     * field
1437
     * Gets content from a DB field
1438
     *
1439
     * @param string $content Input value undergoing processing in this function.
1440
     * @param array $conf stdWrap properties for field.
1441
     * @return string The processed input value
1442
     */
1443
    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...
1444
    {
1445
        return $this->getFieldVal($conf['field']);
1446
    }
1447
1448
    /**
1449
     * current
1450
     * Gets content that has been previously set as 'current'
1451
     * Can be set via setContentToCurrent or setCurrent or will be set automatically i.e. inside the split function
1452
     *
1453
     * @param string $content Input value undergoing processing in this function.
1454
     * @param array $conf stdWrap properties for current.
1455
     * @return string The processed input value
1456
     */
1457
    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...
1458
    {
1459
        return $this->data[$this->currentValKey];
1460
    }
1461
1462
    /**
1463
     * cObject
1464
     * Will replace the content with the value of an official TypoScript cObject
1465
     * like TEXT, COA, HMENU
1466
     *
1467
     * @param string $content Input value undergoing processing in this function.
1468
     * @param array $conf stdWrap properties for cObject.
1469
     * @return string The processed input value
1470
     */
1471
    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...
1472
    {
1473
        return $this->cObjGetSingle($conf['cObject'] ?? '', $conf['cObject.'] ?? [], '/stdWrap/.cObject');
1474
    }
1475
1476
    /**
1477
     * numRows
1478
     * Counts the number of returned records of a DB operation
1479
     * makes use of select internally
1480
     *
1481
     * @param string $content Input value undergoing processing in this function.
1482
     * @param array $conf stdWrap properties for numRows.
1483
     * @return string The processed input value
1484
     */
1485
    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...
1486
    {
1487
        return $this->numRows($conf['numRows.']);
1488
    }
1489
1490
    /**
1491
     * preUserFunc
1492
     * Will execute a user public function before the content will be modified by any other stdWrap function
1493
     *
1494
     * @param string $content Input value undergoing processing in this function.
1495
     * @param array $conf stdWrap properties for preUserFunc.
1496
     * @return string The processed input value
1497
     */
1498
    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...
1499
    {
1500
        return $this->callUserFunction($conf['preUserFunc'], $conf['preUserFunc.'], $content);
1501
    }
1502
1503
    /**
1504
     * stdWrap override hook
1505
     * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
1506
     * this hook will execute functions on existing content but still before the content gets modified or replaced
1507
     *
1508
     * @param string $content Input value undergoing processing in these functions.
1509
     * @param array $conf All stdWrap properties, not just the ones for a particular function.
1510
     * @return string The processed input value
1511
     */
1512
    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...
1513
    {
1514
        foreach ($this->stdWrapHookObjects as $hookObject) {
1515
            /** @var ContentObjectStdWrapHookInterface $hookObject */
1516
            $content = $hookObject->stdWrapOverride($content, $conf, $this);
1517
        }
1518
        return $content;
1519
    }
1520
1521
    /**
1522
     * override
1523
     * Will override the current value of content with its own value'
1524
     *
1525
     * @param string $content Input value undergoing processing in this function.
1526
     * @param array $conf stdWrap properties for override.
1527
     * @return string The processed input value
1528
     */
1529
    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...
1530
    {
1531
        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

1531
        if (trim(/** @scrutinizer ignore-type */ $conf['override'] ?? false)) {
Loading history...
1532
            $content = $conf['override'];
1533
        }
1534
        return $content;
1535
    }
1536
1537
    /**
1538
     * preIfEmptyListNum
1539
     * Gets a value off a CSV list before the following ifEmpty check
1540
     * 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
1541
     *
1542
     * @param string $content Input value undergoing processing in this function.
1543
     * @param array $conf stdWrap properties for preIfEmptyListNum.
1544
     * @return string The processed input value
1545
     */
1546
    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...
1547
    {
1548
        return $this->listNum($content, $conf['preIfEmptyListNum'] ?? null, $conf['preIfEmptyListNum.']['splitChar'] ?? null);
1549
    }
1550
1551
    /**
1552
     * ifNull
1553
     * Will set content to a replacement value in case the value of content is NULL
1554
     *
1555
     * @param string|null $content Input value undergoing processing in this function.
1556
     * @param array $conf stdWrap properties for ifNull.
1557
     * @return string The processed input value
1558
     */
1559
    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...
1560
    {
1561
        return $content ?? $conf['ifNull'];
1562
    }
1563
1564
    /**
1565
     * ifEmpty
1566
     * Will set content to a replacement value in case the trimmed value of content returns FALSE
1567
     * 0 (zero) will be replaced as well
1568
     *
1569
     * @param string $content Input value undergoing processing in this function.
1570
     * @param array $conf stdWrap properties for ifEmpty.
1571
     * @return string The processed input value
1572
     */
1573
    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...
1574
    {
1575
        if (!trim($content)) {
1576
            $content = $conf['ifEmpty'];
1577
        }
1578
        return $content;
1579
    }
1580
1581
    /**
1582
     * ifBlank
1583
     * Will set content to a replacement value in case the trimmed value of content has no length
1584
     * 0 (zero) will not be replaced
1585
     *
1586
     * @param string $content Input value undergoing processing in this function.
1587
     * @param array $conf stdWrap properties for ifBlank.
1588
     * @return string The processed input value
1589
     */
1590
    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...
1591
    {
1592
        if (trim($content) === '') {
1593
            $content = $conf['ifBlank'];
1594
        }
1595
        return $content;
1596
    }
1597
1598
    /**
1599
     * listNum
1600
     * Gets a value off a CSV list after ifEmpty check
1601
     * Might return an empty value in case the CSV does not contain a value at the position given by listNum
1602
     * Use preIfEmptyListNum to avoid that behaviour
1603
     *
1604
     * @param string $content Input value undergoing processing in this function.
1605
     * @param array $conf stdWrap properties for listNum.
1606
     * @return string The processed input value
1607
     */
1608
    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...
1609
    {
1610
        return $this->listNum($content, $conf['listNum'] ?? null, $conf['listNum.']['splitChar'] ?? null);
1611
    }
1612
1613
    /**
1614
     * trim
1615
     * Cuts off any whitespace at the beginning and the end of the content
1616
     *
1617
     * @param string $content Input value undergoing processing in this function.
1618
     * @return string The processed input value
1619
     */
1620
    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...
1621
    {
1622
        return trim($content);
1623
    }
1624
1625
    /**
1626
     * strPad
1627
     * Will return a string padded left/right/on both sides, based on configuration given as stdWrap properties
1628
     *
1629
     * @param string $content Input value undergoing processing in this function.
1630
     * @param array $conf stdWrap properties for strPad.
1631
     * @return string The processed input value
1632
     */
1633
    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...
1634
    {
1635
        // Must specify a length in conf for this to make sense
1636
        $length = (int)$this->stdWrapValue('length', $conf['strPad.'] ?? [], 0);
1637
        // Padding with space is PHP-default
1638
        $padWith = $this->stdWrapValue('padWith', $conf['strPad.'] ?? [], ' ');
1639
        // Padding on the right side is PHP-default
1640
        $padType = STR_PAD_RIGHT;
1641
1642
        if (!empty($conf['strPad.']['type'])) {
1643
            $type = $this->stdWrapValue('type', $conf['strPad.'] ?? []);
1644
            if (strtolower($type) === 'left') {
1645
                $padType = STR_PAD_LEFT;
1646
            } elseif (strtolower($type) === 'both') {
1647
                $padType = STR_PAD_BOTH;
1648
            }
1649
        }
1650
        return str_pad($content, $length, $padWith, $padType);
1651
    }
1652
1653
    /**
1654
     * stdWrap
1655
     * A recursive call of the stdWrap function set
1656
     * This enables the user to execute stdWrap functions in another than the predefined order
1657
     * It modifies the content, not the property
1658
     * while the new feature of chained stdWrap functions modifies the property and not the content
1659
     *
1660
     * @param string $content Input value undergoing processing in this function.
1661
     * @param array $conf stdWrap properties for stdWrap.
1662
     * @return string The processed input value
1663
     */
1664
    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...
1665
    {
1666
        return $this->stdWrap($content, $conf['stdWrap.']);
1667
    }
1668
1669
    /**
1670
     * stdWrap process hook
1671
     * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
1672
     * this hook executes functions directly after the recursive stdWrap function call but still before the content gets modified
1673
     *
1674
     * @param string $content Input value undergoing processing in these functions.
1675
     * @param array $conf All stdWrap properties, not just the ones for a particular function.
1676
     * @return string The processed input value
1677
     */
1678
    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...
1679
    {
1680
        foreach ($this->stdWrapHookObjects as $hookObject) {
1681
            /** @var ContentObjectStdWrapHookInterface $hookObject */
1682
            $content = $hookObject->stdWrapProcess($content, $conf, $this);
1683
        }
1684
        return $content;
1685
    }
1686
1687
    /**
1688
     * required
1689
     * Will immediately stop rendering and return an empty value
1690
     * when there is no content at this point
1691
     *
1692
     * @param string $content Input value undergoing processing in this function.
1693
     * @return string The processed input value
1694
     */
1695
    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...
1696
    {
1697
        if ((string)$content === '') {
1698
            $content = '';
1699
            $this->stopRendering[$this->stdWrapRecursionLevel] = true;
1700
        }
1701
        return $content;
1702
    }
1703
1704
    /**
1705
     * if
1706
     * Will immediately stop rendering and return an empty value
1707
     * when the result of the checks returns FALSE
1708
     *
1709
     * @param string $content Input value undergoing processing in this function.
1710
     * @param array $conf stdWrap properties for if.
1711
     * @return string The processed input value
1712
     */
1713
    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...
1714
    {
1715
        if (empty($conf['if.']) || $this->checkIf($conf['if.'])) {
1716
            return $content;
1717
        }
1718
        $this->stopRendering[$this->stdWrapRecursionLevel] = true;
1719
        return '';
1720
    }
1721
1722
    /**
1723
     * fieldRequired
1724
     * Will immediately stop rendering and return an empty value
1725
     * when there is no content in the field given by fieldRequired
1726
     *
1727
     * @param string $content Input value undergoing processing in this function.
1728
     * @param array $conf stdWrap properties for fieldRequired.
1729
     * @return string The processed input value
1730
     */
1731
    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...
1732
    {
1733
        if (!trim($this->data[$conf['fieldRequired'] ?? null] ?? '')) {
1734
            $content = '';
1735
            $this->stopRendering[$this->stdWrapRecursionLevel] = true;
1736
        }
1737
        return $content;
1738
    }
1739
1740
    /**
1741
     * stdWrap csConv: Converts the input to UTF-8
1742
     *
1743
     * The character set of the input must be specified. Returns the input if
1744
     * matters go wrong, for example if an invalid character set is given.
1745
     *
1746
     * @param string $content The string to convert.
1747
     * @param array $conf stdWrap properties for csConv.
1748
     * @return string The processed input.
1749
     */
1750
    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...
1751
    {
1752
        if (!empty($conf['csConv'])) {
1753
            $output = mb_convert_encoding($content, 'utf-8', trim(strtolower($conf['csConv'])));
1754
            return $output !== false && $output !== '' ? $output : $content;
1755
        }
1756
        return $content;
1757
    }
1758
1759
    /**
1760
     * parseFunc
1761
     * Will parse the content based on functions given as stdWrap properties
1762
     * Heavily used together with RTE based content
1763
     *
1764
     * @param string $content Input value undergoing processing in this function.
1765
     * @param array $conf stdWrap properties for parseFunc.
1766
     * @return string The processed input value
1767
     */
1768
    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...
1769
    {
1770
        return $this->parseFunc($content, $conf['parseFunc.'], $conf['parseFunc']);
1771
    }
1772
1773
    /**
1774
     * HTMLparser
1775
     * Will parse HTML content based on functions given as stdWrap properties
1776
     * Heavily used together with RTE based content
1777
     *
1778
     * @param string $content Input value undergoing processing in this function.
1779
     * @param array $conf stdWrap properties for HTMLparser.
1780
     * @return string The processed input value
1781
     */
1782
    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...
1783
    {
1784
        if (isset($conf['HTMLparser.']) && is_array($conf['HTMLparser.'])) {
1785
            $content = $this->HTMLparser_TSbridge($content, $conf['HTMLparser.']);
1786
        }
1787
        return $content;
1788
    }
1789
1790
    /**
1791
     * split
1792
     * Will split the content by a given token and treat the results separately
1793
     * Automatically fills 'current' with a single result
1794
     *
1795
     * @param string $content Input value undergoing processing in this function.
1796
     * @param array $conf stdWrap properties for split.
1797
     * @return string The processed input value
1798
     */
1799
    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...
1800
    {
1801
        return $this->splitObj($content, $conf['split.']);
1802
    }
1803
1804
    /**
1805
     * replacement
1806
     * Will execute replacements on the content (optionally with preg-regex)
1807
     *
1808
     * @param string $content Input value undergoing processing in this function.
1809
     * @param array $conf stdWrap properties for replacement.
1810
     * @return string The processed input value
1811
     */
1812
    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...
1813
    {
1814
        return $this->replacement($content, $conf['replacement.']);
1815
    }
1816
1817
    /**
1818
     * prioriCalc
1819
     * Will use the content as a mathematical term and calculate the result
1820
     * Can be set to 1 to just get a calculated value or 'intval' to get the integer of the result
1821
     *
1822
     * @param string $content Input value undergoing processing in this function.
1823
     * @param array $conf stdWrap properties for prioriCalc.
1824
     * @return string The processed input value
1825
     */
1826
    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...
1827
    {
1828
        $content = MathUtility::calculateWithParentheses($content);
1829
        if (!empty($conf['prioriCalc']) && $conf['prioriCalc'] === 'intval') {
1830
            $content = (int)$content;
1831
        }
1832
        return $content;
1833
    }
1834
1835
    /**
1836
     * char
1837
     * Returns a one-character string containing the character specified by ascii code.
1838
     *
1839
     * Reliable results only for character codes in the integer range 0 - 127.
1840
     *
1841
     * @see https://php.net/manual/en/function.chr.php
1842
     * @param string $content Input value undergoing processing in this function.
1843
     * @param array $conf stdWrap properties for char.
1844
     * @return string The processed input value
1845
     */
1846
    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...
1847
    {
1848
        return chr((int)$conf['char']);
1849
    }
1850
1851
    /**
1852
     * intval
1853
     * Will return an integer value of the current content
1854
     *
1855
     * @param string $content Input value undergoing processing in this function.
1856
     * @return string The processed input value
1857
     */
1858
    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...
1859
    {
1860
        return (int)$content;
1861
    }
1862
1863
    /**
1864
     * Will return a hashed value of the current content
1865
     *
1866
     * @param string $content Input value undergoing processing in this function.
1867
     * @param array $conf stdWrap properties for hash.
1868
     * @return string The processed input value
1869
     * @link https://php.net/manual/de/function.hash-algos.php for a list of supported hash algorithms
1870
     */
1871
    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...
1872
    {
1873
        $algorithm = $this->stdWrapValue('hash', $conf);
1874
        if (function_exists('hash') && in_array($algorithm, hash_algos())) {
1875
            return hash($algorithm, $content);
1876
        }
1877
        // Non-existing hashing algorithm
1878
        return '';
1879
    }
1880
1881
    /**
1882
     * stdWrap_round will return a rounded number with ceil(), floor() or round(), defaults to round()
1883
     * Only the english number format is supported . (dot) as decimal point
1884
     *
1885
     * @param string $content Input value undergoing processing in this function.
1886
     * @param array $conf stdWrap properties for round.
1887
     * @return string The processed input value
1888
     */
1889
    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...
1890
    {
1891
        return $this->round($content, $conf['round.']);
1892
    }
1893
1894
    /**
1895
     * numberFormat
1896
     * Will return a formatted number based on configuration given as stdWrap properties
1897
     *
1898
     * @param string $content Input value undergoing processing in this function.
1899
     * @param array $conf stdWrap properties for numberFormat.
1900
     * @return string The processed input value
1901
     */
1902
    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...
1903
    {
1904
        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

1904
        return $this->numberFormat(/** @scrutinizer ignore-type */ $content, $conf['numberFormat.'] ?? []);
Loading history...
1905
    }
1906
1907
    /**
1908
     * expandList
1909
     * Will return a formatted number based on configuration given as stdWrap properties
1910
     *
1911
     * @param string $content Input value undergoing processing in this function.
1912
     * @return string The processed input value
1913
     */
1914
    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...
1915
    {
1916
        return GeneralUtility::expandList($content);
1917
    }
1918
1919
    /**
1920
     * date
1921
     * Will return a formatted date based on configuration given according to PHP date/gmdate properties
1922
     * Will return gmdate when the property GMT returns TRUE
1923
     *
1924
     * @param string $content Input value undergoing processing in this function.
1925
     * @param array $conf stdWrap properties for date.
1926
     * @return string The processed input value
1927
     */
1928
    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...
1929
    {
1930
        // Check for zero length string to mimic default case of date/gmdate.
1931
        $content = (string)$content === '' ? $GLOBALS['EXEC_TIME'] : (int)$content;
1932
        $content = !empty($conf['date.']['GMT']) ? gmdate($conf['date'] ?? null, $content) : date($conf['date'] ?? null, $content);
1933
        return $content;
1934
    }
1935
1936
    /**
1937
     * strftime
1938
     * Will return a formatted date based on configuration given according to PHP strftime/gmstrftime properties
1939
     * Will return gmstrftime when the property GMT returns TRUE
1940
     *
1941
     * @param string $content Input value undergoing processing in this function.
1942
     * @param array $conf stdWrap properties for strftime.
1943
     * @return string The processed input value
1944
     */
1945
    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...
1946
    {
1947
        // Check for zero length string to mimic default case of strtime/gmstrftime
1948
        $content = (string)$content === '' ? $GLOBALS['EXEC_TIME'] : (int)$content;
1949
        $content = (isset($conf['strftime.']['GMT']) && $conf['strftime.']['GMT'])
1950
            ? gmstrftime($conf['strftime'] ?? null, $content)
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
1951
            : strftime($conf['strftime'] ?? null, $content);
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
1952
        if (!empty($conf['strftime.']['charset'])) {
1953
            $output = mb_convert_encoding($content, 'utf-8', trim(strtolower($conf['strftime.']['charset'])));
1954
            return $output ?: $content;
1955
        }
1956
        return $content;
1957
    }
1958
1959
    /**
1960
     * strtotime
1961
     * Will return a timestamp based on configuration given according to PHP strtotime
1962
     *
1963
     * @param string $content Input value undergoing processing in this function.
1964
     * @param array $conf stdWrap properties for strtotime.
1965
     * @return string The processed input value
1966
     */
1967
    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...
1968
    {
1969
        if ($conf['strtotime'] !== '1') {
1970
            $content .= ' ' . $conf['strtotime'];
1971
        }
1972
        return strtotime($content, $GLOBALS['EXEC_TIME']);
1973
    }
1974
1975
    /**
1976
     * age
1977
     * Will return the age of a given timestamp based on configuration given by stdWrap properties
1978
     *
1979
     * @param string $content Input value undergoing processing in this function.
1980
     * @param array $conf stdWrap properties for age.
1981
     * @return string The processed input value
1982
     */
1983
    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...
1984
    {
1985
        return $this->calcAge((int)($GLOBALS['EXEC_TIME'] ?? 0) - (int)$content, $conf['age'] ?? null);
1986
    }
1987
1988
    /**
1989
     * case
1990
     * Will transform the content to be upper or lower case only
1991
     * Leaves HTML tags untouched
1992
     *
1993
     * @param string $content Input value undergoing processing in this function.
1994
     * @param array $conf stdWrap properties for case.
1995
     * @return string The processed input value
1996
     */
1997
    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...
1998
    {
1999
        return $this->HTMLcaseshift($content, $conf['case']);
2000
    }
2001
2002
    /**
2003
     * bytes
2004
     * Will return the size of a given number in Bytes	 *
2005
     *
2006
     * @param string $content Input value undergoing processing in this function.
2007
     * @param array $conf stdWrap properties for bytes.
2008
     * @return string The processed input value
2009
     */
2010
    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...
2011
    {
2012
        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

2012
        return GeneralUtility::formatSize(/** @scrutinizer ignore-type */ $content, $conf['bytes.']['labels'], $conf['bytes.']['base']);
Loading history...
2013
    }
2014
2015
    /**
2016
     * substring
2017
     * Will return a substring based on position information given by stdWrap properties
2018
     *
2019
     * @param string $content Input value undergoing processing in this function.
2020
     * @param array $conf stdWrap properties for substring.
2021
     * @return string The processed input value
2022
     */
2023
    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...
2024
    {
2025
        return $this->substring($content, $conf['substring']);
2026
    }
2027
2028
    /**
2029
     * cropHTML
2030
     * Crops content to a given size while leaving HTML tags untouched
2031
     *
2032
     * @param string $content Input value undergoing processing in this function.
2033
     * @param array $conf stdWrap properties for cropHTML.
2034
     * @return string The processed input value
2035
     */
2036
    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...
2037
    {
2038
        return $this->cropHTML($content, $conf['cropHTML'] ?? '');
2039
    }
2040
2041
    /**
2042
     * stripHtml
2043
     * Completely removes HTML tags from content
2044
     *
2045
     * @param string $content Input value undergoing processing in this function.
2046
     * @return string The processed input value
2047
     */
2048
    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...
2049
    {
2050
        return strip_tags($content);
2051
    }
2052
2053
    /**
2054
     * crop
2055
     * Crops content to a given size without caring about HTML tags
2056
     *
2057
     * @param string $content Input value undergoing processing in this function.
2058
     * @param array $conf stdWrap properties for crop.
2059
     * @return string The processed input value
2060
     */
2061
    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...
2062
    {
2063
        return $this->crop($content, $conf['crop']);
2064
    }
2065
2066
    /**
2067
     * rawUrlEncode
2068
     * Encodes content to be used within URLs
2069
     *
2070
     * @param string $content Input value undergoing processing in this function.
2071
     * @return string The processed input value
2072
     */
2073
    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...
2074
    {
2075
        return rawurlencode($content);
2076
    }
2077
2078
    /**
2079
     * htmlSpecialChars
2080
     * Transforms HTML tags to readable text by replacing special characters with their HTML entity
2081
     * When preserveEntities returns TRUE, existing entities will be left untouched
2082
     *
2083
     * @param string $content Input value undergoing processing in this function.
2084
     * @param array $conf stdWrap properties for htmlSpecialChars.
2085
     * @return string The processed input value
2086
     */
2087
    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...
2088
    {
2089
        if (!empty($conf['htmlSpecialChars.']['preserveEntities'])) {
2090
            $content = htmlspecialchars($content, ENT_COMPAT, 'UTF-8', false);
2091
        } else {
2092
            $content = htmlspecialchars($content);
2093
        }
2094
        return $content;
2095
    }
2096
2097
    /**
2098
     * encodeForJavaScriptValue
2099
     * Escapes content to be used inside JavaScript strings. Single quotes are added around the value.
2100
     *
2101
     * @param string $content Input value undergoing processing in this function
2102
     * @return string The processed input value
2103
     */
2104
    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...
2105
    {
2106
        return GeneralUtility::quoteJSvalue($content);
2107
    }
2108
2109
    /**
2110
     * doubleBrTag
2111
     * Searches for double line breaks and replaces them with the given value
2112
     *
2113
     * @param string $content Input value undergoing processing in this function.
2114
     * @param array $conf stdWrap properties for doubleBrTag.
2115
     * @return string The processed input value
2116
     */
2117
    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...
2118
    {
2119
        return preg_replace('/\R{1,2}[\t\x20]*\R{1,2}/', $conf['doubleBrTag'] ?? null, $content);
2120
    }
2121
2122
    /**
2123
     * br
2124
     * Searches for single line breaks and replaces them with a <br />/<br> tag
2125
     * according to the doctype
2126
     *
2127
     * @param string $content Input value undergoing processing in this function.
2128
     * @return string The processed input value
2129
     */
2130
    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...
2131
    {
2132
        return nl2br($content, !empty($this->getTypoScriptFrontendController()->xhtmlDoctype));
2133
    }
2134
2135
    /**
2136
     * brTag
2137
     * Searches for single line feeds and replaces them with the given value
2138
     *
2139
     * @param string $content Input value undergoing processing in this function.
2140
     * @param array $conf stdWrap properties for brTag.
2141
     * @return string The processed input value
2142
     */
2143
    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...
2144
    {
2145
        return str_replace(LF, $conf['brTag'] ?? null, $content);
2146
    }
2147
2148
    /**
2149
     * encapsLines
2150
     * Modifies text blocks by searching for lines which are not surrounded by HTML tags yet
2151
     * and wrapping them with values given by stdWrap properties
2152
     *
2153
     * @param string $content Input value undergoing processing in this function.
2154
     * @param array $conf stdWrap properties for erncapsLines.
2155
     * @return string The processed input value
2156
     */
2157
    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...
2158
    {
2159
        return $this->encaps_lineSplit($content, $conf['encapsLines.']);
2160
    }
2161
2162
    /**
2163
     * keywords
2164
     * Transforms content into a CSV list to be used i.e. as keywords within a meta tag
2165
     *
2166
     * @param string $content Input value undergoing processing in this function.
2167
     * @return string The processed input value
2168
     */
2169
    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...
2170
    {
2171
        return $this->keywords($content);
2172
    }
2173
2174
    /**
2175
     * innerWrap
2176
     * First of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2177
     * See wrap
2178
     *
2179
     * @param string $content Input value undergoing processing in this function.
2180
     * @param array $conf stdWrap properties for innerWrap.
2181
     * @return string The processed input value
2182
     */
2183
    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...
2184
    {
2185
        return $this->wrap($content, $conf['innerWrap'] ?? null);
2186
    }
2187
2188
    /**
2189
     * innerWrap2
2190
     * Second of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2191
     * See wrap
2192
     *
2193
     * @param string $content Input value undergoing processing in this function.
2194
     * @param array $conf stdWrap properties for innerWrap2.
2195
     * @return string The processed input value
2196
     */
2197
    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...
2198
    {
2199
        return $this->wrap($content, $conf['innerWrap2'] ?? null);
2200
    }
2201
2202
    /**
2203
     * preCObject
2204
     * A content object that is prepended to the current content but between the innerWraps and the rest of the wraps
2205
     *
2206
     * @param string $content Input value undergoing processing in this function.
2207
     * @param array $conf stdWrap properties for preCObject.
2208
     * @return string The processed input value
2209
     */
2210
    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...
2211
    {
2212
        return $this->cObjGetSingle($conf['preCObject'], $conf['preCObject.'], '/stdWrap/.preCObject') . $content;
2213
    }
2214
2215
    /**
2216
     * postCObject
2217
     * A content object that is appended to the current content but between the innerWraps and the rest of the wraps
2218
     *
2219
     * @param string $content Input value undergoing processing in this function.
2220
     * @param array $conf stdWrap properties for postCObject.
2221
     * @return string The processed input value
2222
     */
2223
    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...
2224
    {
2225
        return $content . $this->cObjGetSingle($conf['postCObject'], $conf['postCObject.'], '/stdWrap/.postCObject');
2226
    }
2227
2228
    /**
2229
     * wrapAlign
2230
     * Wraps content with a div container having the style attribute text-align set to the given value
2231
     * See wrap
2232
     *
2233
     * @param string $content Input value undergoing processing in this function.
2234
     * @param array $conf stdWrap properties for wrapAlign.
2235
     * @return string The processed input value
2236
     */
2237
    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...
2238
    {
2239
        $wrapAlign = trim($conf['wrapAlign'] ?? '');
2240
        if ($wrapAlign) {
2241
            $content = $this->wrap($content, '<div style="text-align:' . htmlspecialchars($wrapAlign) . ';">|</div>');
2242
        }
2243
        return $content;
2244
    }
2245
2246
    /**
2247
     * typolink
2248
     * Wraps the content with a link tag
2249
     * URLs and other attributes are created automatically by the values given in the stdWrap properties
2250
     * See wrap
2251
     *
2252
     * @param string $content Input value undergoing processing in this function.
2253
     * @param array $conf stdWrap properties for typolink.
2254
     * @return string The processed input value
2255
     */
2256
    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...
2257
    {
2258
        return $this->typoLink($content, $conf['typolink.']);
2259
    }
2260
2261
    /**
2262
     * wrap
2263
     * This is the "mother" of all wraps
2264
     * Third of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2265
     * Basically it will put additional content before and after the current content using a split character as a placeholder for the current content
2266
     * The default split character is | but it can be replaced with other characters by the property splitChar
2267
     * Any other wrap that does not have own splitChar settings will be using the default split char though
2268
     *
2269
     * @param string $content Input value undergoing processing in this function.
2270
     * @param array $conf stdWrap properties for wrap.
2271
     * @return string The processed input value
2272
     */
2273
    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...
2274
    {
2275
        return $this->wrap(
2276
            $content,
2277
            $conf['wrap'] ?? null,
2278
            $conf['wrap.']['splitChar'] ?? '|'
2279
        );
2280
    }
2281
2282
    /**
2283
     * noTrimWrap
2284
     * Fourth of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2285
     * The major difference to any other wrap is, that this one can make use of whitespace without trimming	 *
2286
     *
2287
     * @param string $content Input value undergoing processing in this function.
2288
     * @param array $conf stdWrap properties for noTrimWrap.
2289
     * @return string The processed input value
2290
     */
2291
    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...
2292
    {
2293
        $splitChar = isset($conf['noTrimWrap.']['splitChar.'])
2294
            ? $this->stdWrap($conf['noTrimWrap.']['splitChar'] ?? '', $conf['noTrimWrap.']['splitChar.'])
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
2295
            : $conf['noTrimWrap.']['splitChar'] ?? '';
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
2296
        if ($splitChar === null || $splitChar === '') {
2297
            $splitChar = '|';
2298
        }
2299
        $content = $this->noTrimWrap(
2300
            $content,
2301
            $conf['noTrimWrap'],
2302
            $splitChar
2303
        );
2304
        return $content;
2305
    }
2306
2307
    /**
2308
     * wrap2
2309
     * Fifth of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2310
     * The default split character is | but it can be replaced with other characters by the property splitChar
2311
     *
2312
     * @param string $content Input value undergoing processing in this function.
2313
     * @param array $conf stdWrap properties for wrap2.
2314
     * @return string The processed input value
2315
     */
2316
    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...
2317
    {
2318
        return $this->wrap(
2319
            $content,
2320
            $conf['wrap2'] ?? null,
2321
            $conf['wrap2.']['splitChar'] ?? '|'
2322
        );
2323
    }
2324
2325
    /**
2326
     * dataWrap
2327
     * Sixth of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2328
     * 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
2329
     *
2330
     * @param string $content Input value undergoing processing in this function.
2331
     * @param array $conf stdWrap properties for dataWrap.
2332
     * @return string The processed input value
2333
     */
2334
    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...
2335
    {
2336
        return $this->dataWrap($content, $conf['dataWrap']);
2337
    }
2338
2339
    /**
2340
     * prepend
2341
     * A content object that will be prepended to the current content after most of the wraps have already been applied
2342
     *
2343
     * @param string $content Input value undergoing processing in this function.
2344
     * @param array $conf stdWrap properties for prepend.
2345
     * @return string The processed input value
2346
     */
2347
    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...
2348
    {
2349
        return $this->cObjGetSingle($conf['prepend'], $conf['prepend.'], '/stdWrap/.prepend') . $content;
2350
    }
2351
2352
    /**
2353
     * append
2354
     * A content object that will be appended to the current content after most of the wraps have already been applied
2355
     *
2356
     * @param string $content Input value undergoing processing in this function.
2357
     * @param array $conf stdWrap properties for append.
2358
     * @return string The processed input value
2359
     */
2360
    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...
2361
    {
2362
        return $content . $this->cObjGetSingle($conf['append'], $conf['append.'], '/stdWrap/.append');
2363
    }
2364
2365
    /**
2366
     * wrap3
2367
     * Seventh of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2368
     * The default split character is | but it can be replaced with other characters by the property splitChar
2369
     *
2370
     * @param string $content Input value undergoing processing in this function.
2371
     * @param array $conf stdWrap properties for wrap3.
2372
     * @return string The processed input value
2373
     */
2374
    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...
2375
    {
2376
        return $this->wrap(
2377
            $content,
2378
            $conf['wrap3'] ?? null,
2379
            $conf['wrap3.']['splitChar'] ?? '|'
2380
        );
2381
    }
2382
2383
    /**
2384
     * orderedStdWrap
2385
     * Calls stdWrap for each entry in the provided array
2386
     *
2387
     * @param string $content Input value undergoing processing in this function.
2388
     * @param array $conf stdWrap properties for orderedStdWrap.
2389
     * @return string The processed input value
2390
     */
2391
    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...
2392
    {
2393
        $sortedKeysArray = ArrayUtility::filterAndSortByNumericKeys($conf['orderedStdWrap.'], true);
2394
        foreach ($sortedKeysArray as $key) {
2395
            $content = $this->stdWrap($content, $conf['orderedStdWrap.'][$key . '.'] ?? null);
2396
        }
2397
        return $content;
2398
    }
2399
2400
    /**
2401
     * outerWrap
2402
     * Eighth of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2403
     *
2404
     * @param string $content Input value undergoing processing in this function.
2405
     * @param array $conf stdWrap properties for outerWrap.
2406
     * @return string The processed input value
2407
     */
2408
    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...
2409
    {
2410
        return $this->wrap($content, $conf['outerWrap'] ?? null);
2411
    }
2412
2413
    /**
2414
     * insertData
2415
     * Can fetch additional content the same way data does and replaces any occurrence of {field:whatever} with this content
2416
     *
2417
     * @param string $content Input value undergoing processing in this function.
2418
     * @return string The processed input value
2419
     */
2420
    public function stdWrap_insertData($content = '')
0 ignored issues
show
Coding Style introduced by
Method name "ContentObjectRenderer::stdWrap_insertData" is not in camel caps format
Loading history...
2421
    {
2422
        return $this->insertData($content);
2423
    }
2424
2425
    /**
2426
     * postUserFunc
2427
     * Will execute a user function after the content has been modified by any other stdWrap function
2428
     *
2429
     * @param string $content Input value undergoing processing in this function.
2430
     * @param array $conf stdWrap properties for postUserFunc.
2431
     * @return string The processed input value
2432
     */
2433
    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...
2434
    {
2435
        return $this->callUserFunction($conf['postUserFunc'], $conf['postUserFunc.'], $content);
2436
    }
2437
2438
    /**
2439
     * postUserFuncInt
2440
     * Will execute a user function after the content has been created and each time it is fetched from Cache
2441
     * The result of this function itself will not be cached
2442
     *
2443
     * @param string $content Input value undergoing processing in this function.
2444
     * @param array $conf stdWrap properties for postUserFuncInt.
2445
     * @return string The processed input value
2446
     */
2447
    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...
2448
    {
2449
        $substKey = 'INT_SCRIPT.' . $this->getTypoScriptFrontendController()->uniqueHash();
2450
        $this->getTypoScriptFrontendController()->config['INTincScript'][$substKey] = [
2451
            'content' => $content,
2452
            'postUserFunc' => $conf['postUserFuncInt'],
2453
            'conf' => $conf['postUserFuncInt.'],
2454
            'type' => 'POSTUSERFUNC',
2455
            'cObj' => serialize($this)
2456
        ];
2457
        $content = '<!--' . $substKey . '-->';
2458
        return $content;
2459
    }
2460
2461
    /**
2462
     * prefixComment
2463
     * Will add HTML comments to the content to make it easier to identify certain content elements within the HTML output later on
2464
     *
2465
     * @param string $content Input value undergoing processing in this function.
2466
     * @param array $conf stdWrap properties for prefixComment.
2467
     * @return string The processed input value
2468
     */
2469
    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...
2470
    {
2471
        if (
2472
            (!isset($this->getTypoScriptFrontendController()->config['config']['disablePrefixComment']) || !$this->getTypoScriptFrontendController()->config['config']['disablePrefixComment'])
2473
            && !empty($conf['prefixComment'])
2474
        ) {
2475
            $content = $this->prefixComment($conf['prefixComment'], [], $content);
2476
        }
2477
        return $content;
2478
    }
2479
2480
    /**
2481
     * editIcons
2482
     * Will render icons for frontend editing as long as there is a BE user logged in
2483
     *
2484
     * @param string $content Input value undergoing processing in this function.
2485
     * @param array $conf stdWrap properties for editIcons.
2486
     * @return string The processed input value
2487
     */
2488
    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...
2489
    {
2490
        if ($this->getTypoScriptFrontendController()->isBackendUserLoggedIn() && $conf['editIcons']) {
2491
            if (!isset($conf['editIcons.']) || !is_array($conf['editIcons.'])) {
2492
                $conf['editIcons.'] = [];
2493
            }
2494
            $content = $this->editIcons($content, $conf['editIcons'], $conf['editIcons.']);
2495
        }
2496
        return $content;
2497
    }
2498
2499
    /**
2500
     * editPanel
2501
     * Will render the edit panel for frontend editing as long as there is a BE user logged in
2502
     *
2503
     * @param string $content Input value undergoing processing in this function.
2504
     * @param array $conf stdWrap properties for editPanel.
2505
     * @return string The processed input value
2506
     */
2507
    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...
2508
    {
2509
        if ($this->getTypoScriptFrontendController()->isBackendUserLoggedIn()) {
2510
            $content = $this->editPanel($content, $conf['editPanel.']);
2511
        }
2512
        return $content;
2513
    }
2514
2515
    /**
2516
     * Store content into cache
2517
     *
2518
     * @param string $content Input value undergoing processing in these functions.
2519
     * @param array $conf All stdWrap properties, not just the ones for a particular function.
2520
     * @return string The processed input value
2521
     */
2522
    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...
2523
    {
2524
        if (!isset($conf['cache.'])) {
2525
            return $content;
2526
        }
2527
        $key = $this->calculateCacheKey($conf['cache.']);
2528
        if (empty($key)) {
2529
            return $content;
2530
        }
2531
        /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cacheFrontend */
2532
        $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)->getCache('hash');
2533
        $tags = $this->calculateCacheTags($conf['cache.']);
2534
        $lifetime = $this->calculateCacheLifetime($conf['cache.']);
2535
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap_cacheStore'] ?? [] as $_funcRef) {
2536
            $params = [
2537
                'key' => $key,
2538
                'content' => $content,
2539
                'lifetime' => $lifetime,
2540
                'tags' => $tags
2541
            ];
2542
            $ref = $this; // introduced for phpstan to not lose type information when passing $this into callUserFunction
2543
            GeneralUtility::callUserFunction($_funcRef, $params, $ref);
2544
        }
2545
        $cacheFrontend->set($key, $content, $tags, $lifetime);
2546
        return $content;
2547
    }
2548
2549
    /**
2550
     * stdWrap post process hook
2551
     * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
2552
     * this hook executes functions at after the content has been modified by the rest of the stdWrap functions but still before debugging
2553
     *
2554
     * @param string $content Input value undergoing processing in these functions.
2555
     * @param array $conf All stdWrap properties, not just the ones for a particular function.
2556
     * @return string The processed input value
2557
     */
2558
    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...
2559
    {
2560
        foreach ($this->stdWrapHookObjects as $hookObject) {
2561
            /** @var ContentObjectStdWrapHookInterface $hookObject */
2562
            $content = $hookObject->stdWrapPostProcess($content, $conf, $this);
2563
        }
2564
        return $content;
2565
    }
2566
2567
    /**
2568
     * debug
2569
     * Will output the content as readable HTML code
2570
     *
2571
     * @param string $content Input value undergoing processing in this function.
2572
     * @return string The processed input value
2573
     */
2574
    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...
2575
    {
2576
        return '<pre>' . htmlspecialchars($content) . '</pre>';
2577
    }
2578
2579
    /**
2580
     * debugFunc
2581
     * Will output the content in a debug table
2582
     *
2583
     * @param string $content Input value undergoing processing in this function.
2584
     * @param array $conf stdWrap properties for debugFunc.
2585
     * @return string The processed input value
2586
     */
2587
    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...
2588
    {
2589
        debug((int)$conf['debugFunc'] === 2 ? [$content] : $content);
2590
        return $content;
2591
    }
2592
2593
    /**
2594
     * debugData
2595
     * Will output the data used by the current record in a debug table
2596
     *
2597
     * @param string $content Input value undergoing processing in this function.
2598
     * @return string The processed input value
2599
     */
2600
    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...
2601
    {
2602
        debug($this->data, '$cObj->data:');
2603
        if (is_array($this->alternativeData)) {
0 ignored issues
show
introduced by
The condition is_array($this->alternativeData) is always false.
Loading history...
2604
            debug($this->alternativeData, '$this->alternativeData');
2605
        }
2606
        return $content;
2607
    }
2608
2609
    /**
2610
     * Returns number of rows selected by the query made by the properties set.
2611
     * Implements the stdWrap "numRows" property
2612
     *
2613
     * @param array $conf TypoScript properties for the property (see link to "numRows")
2614
     * @return int The number of rows found by the select
2615
     * @internal
2616
     * @see stdWrap()
2617
     */
2618
    public function numRows($conf)
2619
    {
2620
        $conf['select.']['selectFields'] = 'count(*)';
2621
        $statement = $this->exec_getQuery($conf['table'], $conf['select.']);
2622
2623
        return (int)$statement->fetchColumn(0);
2624
    }
2625
2626
    /**
2627
     * Exploding a string by the $char value (if integer its an ASCII value) and returning index $listNum
2628
     *
2629
     * @param string $content String to explode
2630
     * @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())
2631
     * @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.
2632
     * @return string
2633
     */
2634
    public function listNum($content, $listNum, $char)
2635
    {
2636
        $char = $char ?: ',';
2637
        if (MathUtility::canBeInterpretedAsInteger($char)) {
2638
            $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

2638
            $char = chr(/** @scrutinizer ignore-type */ $char);
Loading history...
2639
        }
2640
        $temp = explode($char, $content);
2641
        $last = '' . (count($temp) - 1);
2642
        // Take a random item if requested
2643
        if ($listNum === 'rand') {
2644
            $listNum = random_int(0, count($temp) - 1);
2645
        }
2646
        $index = $this->calc(str_ireplace('last', $last, $listNum));
2647
        return $temp[$index];
2648
    }
2649
2650
    /**
2651
     * Compares values together based on the settings in the input TypoScript array and returns the comparison result.
2652
     * Implements the "if" function in TYPO3 TypoScript
2653
     *
2654
     * @param array $conf TypoScript properties defining what to compare
2655
     * @return bool
2656
     * @see stdWrap()
2657
     * @see _parseFunc()
2658
     */
2659
    public function checkIf($conf)
2660
    {
2661
        if (!is_array($conf)) {
0 ignored issues
show
introduced by
The condition is_array($conf) is always true.
Loading history...
2662
            return true;
2663
        }
2664
        if (isset($conf['directReturn'])) {
2665
            return (bool)$conf['directReturn'];
2666
        }
2667
        $flag = true;
2668
        if (isset($conf['isNull.'])) {
2669
            $isNull = $this->stdWrap('', $conf['isNull.']);
2670
            if ($isNull !== null) {
0 ignored issues
show
introduced by
The condition $isNull !== null is always true.
Loading history...
2671
                $flag = false;
2672
            }
2673
        }
2674
        if (isset($conf['isTrue']) || isset($conf['isTrue.'])) {
2675
            $isTrue = trim($this->stdWrapValue('isTrue', $conf));
2676
            if (!$isTrue) {
2677
                $flag = false;
2678
            }
2679
        }
2680
        if (isset($conf['isFalse']) || isset($conf['isFalse.'])) {
2681
            $isFalse = trim($this->stdWrapValue('isFalse', $conf));
2682
            if ($isFalse) {
2683
                $flag = false;
2684
            }
2685
        }
2686
        if (isset($conf['isPositive']) || isset($conf['isPositive.'])) {
2687
            $number = $this->calc($this->stdWrapValue('isPositive', $conf));
2688
            if ($number < 1) {
2689
                $flag = false;
2690
            }
2691
        }
2692
        if ($flag) {
2693
            $value = trim($this->stdWrapValue('value', $conf));
2694
            if (isset($conf['isGreaterThan']) || isset($conf['isGreaterThan.'])) {
2695
                $number = trim($this->stdWrapValue('isGreaterThan', $conf));
2696
                if ($number <= $value) {
2697
                    $flag = false;
2698
                }
2699
            }
2700
            if (isset($conf['isLessThan']) || isset($conf['isLessThan.'])) {
2701
                $number = trim($this->stdWrapValue('isLessThan', $conf));
2702
                if ($number >= $value) {
2703
                    $flag = false;
2704
                }
2705
            }
2706
            if (isset($conf['equals']) || isset($conf['equals.'])) {
2707
                $number = trim($this->stdWrapValue('equals', $conf));
2708
                if ($number != $value) {
2709
                    $flag = false;
2710
                }
2711
            }
2712
            if (isset($conf['isInList']) || isset($conf['isInList.'])) {
2713
                $number = trim($this->stdWrapValue('isInList', $conf));
2714
                if (!GeneralUtility::inList($value, $number)) {
2715
                    $flag = false;
2716
                }
2717
            }
2718
            if (isset($conf['bitAnd']) || isset($conf['bitAnd.'])) {
2719
                $number = trim($this->stdWrapValue('bitAnd', $conf));
2720
                if ((new BitSet($number))->get($value) === false) {
0 ignored issues
show
Bug introduced by
$number of type string is incompatible with the type integer expected by parameter $set of TYPO3\CMS\Core\Type\BitSet::__construct(). ( Ignorable by Annotation )

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

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

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

2720
                if ((new BitSet($number))->get(/** @scrutinizer ignore-type */ $value) === false) {
Loading history...
2721
                    $flag = false;
2722
                }
2723
            }
2724
        }
2725
        if ($conf['negate'] ?? false) {
2726
            $flag = !$flag;
2727
        }
2728
        return $flag;
2729
    }
2730
2731
    /**
2732
     * Passes the input value, $theValue, to an instance of "\TYPO3\CMS\Core\Html\HtmlParser"
2733
     * together with the TypoScript options which are first converted from a TS style array
2734
     * to a set of arrays with options for the \TYPO3\CMS\Core\Html\HtmlParser class.
2735
     *
2736
     * @param string $theValue The value to parse by the class \TYPO3\CMS\Core\Html\HtmlParser
2737
     * @param array $conf TypoScript properties for the parser. See link.
2738
     * @return string Return value.
2739
     * @see stdWrap()
2740
     * @see \TYPO3\CMS\Core\Html\HtmlParser::HTMLparserConfig()
2741
     * @see \TYPO3\CMS\Core\Html\HtmlParser::HTMLcleaner()
2742
     */
2743
    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...
2744
    {
2745
        $htmlParser = GeneralUtility::makeInstance(HtmlParser::class);
2746
        $htmlParserCfg = $htmlParser->HTMLparserConfig($conf);
2747
        return $htmlParser->HTMLcleaner($theValue, $htmlParserCfg[0], $htmlParserCfg[1], $htmlParserCfg[2], $htmlParserCfg[3]);
2748
    }
2749
2750
    /**
2751
     * Wrapping input value in a regular "wrap" but parses the wrapping value first for "insertData" codes.
2752
     *
2753
     * @param string $content Input string being wrapped
2754
     * @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.
2755
     * @return string Output string wrapped in the wrapping value.
2756
     * @see insertData()
2757
     * @see stdWrap()
2758
     */
2759
    public function dataWrap($content, $wrap)
2760
    {
2761
        return $this->wrap($content, $this->insertData($wrap));
2762
    }
2763
2764
    /**
2765
     * Implements the "insertData" property of stdWrap meaning that if strings matching {...} is found in the input string they
2766
     * will be substituted with the return value from getData (datatype) which is passed the content of the curly braces.
2767
     * If the content inside the curly braces starts with a hash sign {#...} it is a field name that must be quoted by Doctrine
2768
     * DBAL and is skipped here for later processing.
2769
     *
2770
     * Example: If input string is "This is the page title: {page:title}" then the part, '{page:title}', will be substituted with
2771
     * the current pages title field value.
2772
     *
2773
     * @param string $str Input value
2774
     * @return string Processed input value
2775
     * @see getData()
2776
     * @see stdWrap()
2777
     * @see dataWrap()
2778
     */
2779
    public function insertData($str)
2780
    {
2781
        $inside = 0;
2782
        $newVal = '';
2783
        $pointer = 0;
2784
        $totalLen = strlen($str);
2785
        do {
2786
            if (!$inside) {
2787
                $len = strcspn(substr($str, $pointer), '{');
2788
                $newVal .= substr($str, $pointer, $len);
2789
                $inside = true;
2790
                if (substr($str, $pointer + $len + 1, 1) === '#') {
2791
                    $len2 = strcspn(substr($str, $pointer + $len), '}');
2792
                    $newVal .= substr($str, $pointer + $len, $len2);
2793
                    $len += $len2;
2794
                    $inside = false;
2795
                }
2796
            } else {
2797
                $len = strcspn(substr($str, $pointer), '}') + 1;
2798
                $newVal .= $this->getData(substr($str, $pointer + 1, $len - 2), $this->data);
2799
                $inside = false;
2800
            }
2801
            $pointer += $len;
2802
        } while ($pointer < $totalLen);
2803
        return $newVal;
2804
    }
2805
2806
    /**
2807
     * 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.
2808
     * Notice; this function (used by stdWrap) can be disabled by a "config.disablePrefixComment" setting in TypoScript.
2809
     *
2810
     * @param string $str Input value
2811
     * @param array $conf TypoScript Configuration (not used at this point.)
2812
     * @param string $content The content to wrap the comment around.
2813
     * @return string Processed input value
2814
     * @see stdWrap()
2815
     */
2816
    public function prefixComment($str, $conf, $content)
2817
    {
2818
        if (empty($str)) {
2819
            return $content;
2820
        }
2821
        $parts = explode('|', $str);
2822
        $indent = (int)$parts[0];
2823
        $comment = htmlspecialchars($this->insertData($parts[1]));
2824
        $output = LF
2825
            . str_pad('', $indent, "\t") . '<!-- ' . $comment . ' [begin] -->' . LF
2826
            . str_pad('', $indent + 1, "\t") . $content . LF
2827
            . str_pad('', $indent, "\t") . '<!-- ' . $comment . ' [end] -->' . LF
2828
            . str_pad('', $indent + 1, "\t");
2829
        return $output;
2830
    }
2831
2832
    /**
2833
     * Implements the stdWrap property "substring" which is basically a TypoScript implementation of the PHP function, substr()
2834
     *
2835
     * @param string $content The string to perform the operation on
2836
     * @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().
2837
     * @return string The processed input value.
2838
     * @internal
2839
     * @see stdWrap()
2840
     */
2841
    public function substring($content, $options)
2842
    {
2843
        $options = GeneralUtility::intExplode(',', $options . ',');
2844
        if ($options[1]) {
2845
            return mb_substr($content, $options[0], $options[1], 'utf-8');
2846
        }
2847
        return mb_substr($content, $options[0], null, 'utf-8');
2848
    }
2849
2850
    /**
2851
     * 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.
2852
     *
2853
     * @param string $content The string to perform the operation on
2854
     * @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.
2855
     * @return string The processed input value.
2856
     * @internal
2857
     * @see stdWrap()
2858
     */
2859
    public function crop($content, $options)
2860
    {
2861
        $options = explode('|', $options);
2862
        $chars = (int)$options[0];
2863
        $afterstring = trim($options[1] ?? '');
2864
        $crop2space = trim($options[2] ?? '');
2865
        if ($chars) {
2866
            if (mb_strlen($content, 'utf-8') > abs($chars)) {
2867
                $truncatePosition = false;
2868
                if ($chars < 0) {
2869
                    $content = mb_substr($content, $chars, null, 'utf-8');
2870
                    if ($crop2space) {
2871
                        $truncatePosition = strpos($content, ' ');
2872
                    }
2873
                    $content = $truncatePosition ? $afterstring . substr($content, $truncatePosition) : $afterstring . $content;
2874
                } else {
2875
                    $content = mb_substr($content, 0, $chars, 'utf-8');
2876
                    if ($crop2space) {
2877
                        $truncatePosition = strrpos($content, ' ');
2878
                    }
2879
                    $content = $truncatePosition ? substr($content, 0, $truncatePosition) . $afterstring : $content . $afterstring;
2880
                }
2881
            }
2882
        }
2883
        return $content;
2884
    }
2885
2886
    /**
2887
     * Implements the stdWrap property "cropHTML" which is a modified "substr" function allowing to limit a string length
2888
     * to a certain number of chars (from either start or end of string) and having a pre/postfix applied if the string
2889
     * really was cropped.
2890
     *
2891
     * Compared to stdWrap.crop it respects HTML tags and entities.
2892
     *
2893
     * @param string $content The string to perform the operation on
2894
     * @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.
2895
     * @return string The processed input value.
2896
     * @internal
2897
     * @see stdWrap()
2898
     */
2899
    public function cropHTML($content, $options)
2900
    {
2901
        $options = explode('|', $options);
2902
        $chars = (int)$options[0];
2903
        $absChars = abs($chars);
2904
        $replacementForEllipsis = trim($options[1] ?? '');
2905
        $crop2space = trim($options[2] ?? '') === '1';
2906
        // Split $content into an array(even items in the array are outside the tags, odd numbers are tag-blocks).
2907
        $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';
2908
        $tagsRegEx = '
2909
			(
2910
				(?:
2911
					<!--.*?-->					# a comment
2912
					|
2913
					<canvas[^>]*>.*?</canvas>   # a canvas tag
2914
					|
2915
					<script[^>]*>.*?</script>   # a script tag
2916
					|
2917
					<noscript[^>]*>.*?</noscript> # a noscript tag
2918
					|
2919
					<template[^>]*>.*?</template> # a template tag
2920
				)
2921
				|
2922
				</?(?:' . $tags . ')+			# opening tag (\'<tag\') or closing tag (\'</tag\')
2923
				(?:
2924
					(?:
2925
						(?:
2926
							\\s+\\w[\\w-]*		# EITHER spaces, followed by attribute names
2927
							(?:
2928
								\\s*=?\\s*		# equals
2929
								(?>
2930
									".*?"		# attribute values in double-quotes
2931
									|
2932
									\'.*?\'		# attribute values in single-quotes
2933
									|
2934
									[^\'">\\s]+	# plain attribute values
2935
								)
2936
							)?
2937
						)
2938
						|						# OR a single dash (for TYPO3 link tag)
2939
						(?:
2940
							\\s+-
2941
						)
2942
					)+\\s*
2943
					|							# OR only spaces
2944
					\\s*
2945
				)
2946
				/?>								# closing the tag with \'>\' or \'/>\'
2947
			)';
2948
        $splittedContent = preg_split('%' . $tagsRegEx . '%xs', $content, -1, PREG_SPLIT_DELIM_CAPTURE);
2949
        // Reverse array if we are cropping from right.
2950
        if ($chars < 0) {
2951
            $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

2951
            $splittedContent = array_reverse(/** @scrutinizer ignore-type */ $splittedContent);
Loading history...
2952
        }
2953
        // Crop the text (chars of tag-blocks are not counted).
2954
        $strLen = 0;
2955
        // This is the offset of the content item which was cropped.
2956
        $croppedOffset = null;
2957
        $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

2957
        $countSplittedContent = count(/** @scrutinizer ignore-type */ $splittedContent);
Loading history...
2958
        for ($offset = 0; $offset < $countSplittedContent; $offset++) {
2959
            if ($offset % 2 === 0) {
2960
                $tempContent = $splittedContent[$offset];
2961
                $thisStrLen = mb_strlen(html_entity_decode($tempContent, ENT_COMPAT, 'UTF-8'), 'utf-8');
2962
                if ($strLen + $thisStrLen > $absChars) {
2963
                    $croppedOffset = $offset;
2964
                    $cropPosition = $absChars - $strLen;
2965
                    // The snippet "&[^&\s;]{2,8};" in the RegEx below represents entities.
2966
                    $patternMatchEntityAsSingleChar = '(&[^&\\s;]{2,8};|.)';
2967
                    $cropRegEx = $chars < 0 ? '#' . $patternMatchEntityAsSingleChar . '{0,' . ($cropPosition + 1) . '}$#uis' : '#^' . $patternMatchEntityAsSingleChar . '{0,' . ($cropPosition + 1) . '}#uis';
2968
                    if (preg_match($cropRegEx, $tempContent, $croppedMatch)) {
2969
                        $tempContentPlusOneCharacter = $croppedMatch[0];
2970
                    } else {
2971
                        $tempContentPlusOneCharacter = false;
2972
                    }
2973
                    $cropRegEx = $chars < 0 ? '#' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}$#uis' : '#^' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}#uis';
2974
                    if (preg_match($cropRegEx, $tempContent, $croppedMatch)) {
2975
                        $tempContent = $croppedMatch[0];
2976
                        if ($crop2space && $tempContentPlusOneCharacter !== false) {
2977
                            $cropRegEx = $chars < 0 ? '#(?<=\\s)' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}$#uis' : '#^' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}(?=\\s)#uis';
2978
                            if (preg_match($cropRegEx, $tempContentPlusOneCharacter, $croppedMatch)) {
2979
                                $tempContent = $croppedMatch[0];
2980
                            }
2981
                        }
2982
                    }
2983
                    $splittedContent[$offset] = $tempContent;
2984
                    break;
2985
                }
2986
                $strLen += $thisStrLen;
2987
            }
2988
        }
2989
        // Close cropped tags.
2990
        $closingTags = [];
2991
        if ($croppedOffset !== null) {
2992
            $openingTagRegEx = '#^<(\\w+)(?:\\s|>)#';
2993
            $closingTagRegEx = '#^</(\\w+)(?:\\s|>)#';
2994
            for ($offset = $croppedOffset - 1; $offset >= 0; $offset = $offset - 2) {
2995
                if (substr($splittedContent[$offset], -2) === '/>') {
2996
                    // Ignore empty element tags (e.g. <br />).
2997
                    continue;
2998
                }
2999
                preg_match($chars < 0 ? $closingTagRegEx : $openingTagRegEx, $splittedContent[$offset], $matches);
3000
                $tagName = $matches[1] ?? null;
3001
                if ($tagName !== null) {
3002
                    // Seek for the closing (or opening) tag.
3003
                    $countSplittedContent = count($splittedContent);
3004
                    for ($seekingOffset = $offset + 2; $seekingOffset < $countSplittedContent; $seekingOffset = $seekingOffset + 2) {
3005
                        preg_match($chars < 0 ? $openingTagRegEx : $closingTagRegEx, $splittedContent[$seekingOffset], $matches);
3006
                        $seekingTagName = $matches[1] ?? null;
3007
                        if ($tagName === $seekingTagName) {
3008
                            // We found a matching tag.
3009
                            // Add closing tag only if it occurs after the cropped content item.
3010
                            if ($seekingOffset > $croppedOffset) {
3011
                                $closingTags[] = $splittedContent[$seekingOffset];
3012
                            }
3013
                            break;
3014
                        }
3015
                    }
3016
                }
3017
            }
3018
            // Drop the cropped items of the content array. The $closingTags will be added later on again.
3019
            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

3019
            array_splice(/** @scrutinizer ignore-type */ $splittedContent, $croppedOffset + 1);
Loading history...
3020
        }
3021
        $splittedContent = array_merge($splittedContent, [
3022
            $croppedOffset !== null ? $replacementForEllipsis : ''
3023
        ], $closingTags);
3024
        // Reverse array once again if we are cropping from the end.
3025
        if ($chars < 0) {
3026
            $splittedContent = array_reverse($splittedContent);
3027
        }
3028
        return implode('', $splittedContent);
3029
    }
3030
3031
    /**
3032
     * 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())
3033
     *
3034
     * @param string $val The string to evaluate. Example: "3+4*10/5" will generate "35". Only integer numbers can be used.
3035
     * @return int The result (might be a float if you did a division of the numbers).
3036
     * @see \TYPO3\CMS\Core\Utility\MathUtility::calculateWithPriorityToAdditionAndSubtraction()
3037
     */
3038
    public function calc($val)
3039
    {
3040
        $parts = GeneralUtility::splitCalc($val, '+-*/');
3041
        $value = 0;
3042
        foreach ($parts as $part) {
3043
            $theVal = $part[1];
3044
            $sign = $part[0];
3045
            if ((string)(int)$theVal === (string)$theVal) {
3046
                $theVal = (int)$theVal;
3047
            } else {
3048
                $theVal = 0;
3049
            }
3050
            if ($sign === '-') {
3051
                $value -= $theVal;
3052
            }
3053
            if ($sign === '+') {
3054
                $value += $theVal;
3055
            }
3056
            if ($sign === '/') {
3057
                if ((int)$theVal) {
3058
                    $value /= (int)$theVal;
3059
                }
3060
            }
3061
            if ($sign === '*') {
3062
                $value *= $theVal;
3063
            }
3064
        }
3065
        return $value;
3066
    }
3067
3068
    /**
3069
     * 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.
3070
     * 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.
3071
     * Implements the "optionSplit" processing of the TypoScript options for each splitted value to parse.
3072
     *
3073
     * @param string $value The string value to explode by $conf[token] and process each part
3074
     * @param array $conf TypoScript properties for "split
3075
     * @return string Compiled result
3076
     * @internal
3077
     * @see stdWrap()
3078
     * @see \TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject::processItemStates()
3079
     */
3080
    public function splitObj($value, $conf)
3081
    {
3082
        $conf['token'] = isset($conf['token.']) ? $this->stdWrap($conf['token'], $conf['token.']) : $conf['token'];
3083
        if ($conf['token'] === '') {
3084
            return $value;
3085
        }
3086
        $valArr = explode($conf['token'], $value);
3087
3088
        // return value directly by returnKey. No further processing
3089
        if (!empty($valArr) && (MathUtility::canBeInterpretedAsInteger($conf['returnKey'] ?? null) || ($conf['returnKey.'] ?? false))) {
3090
            $key = (int)$this->stdWrapValue('returnKey', $conf);
3091
            return $valArr[$key] ?? '';
3092
        }
3093
3094
        // return the amount of elements. No further processing
3095
        if (!empty($valArr) && ($conf['returnCount'] || $conf['returnCount.'])) {
3096
            $returnCount = (bool)$this->stdWrapValue('returnCount', $conf);
3097
            return $returnCount ? count($valArr) : 0;
3098
        }
3099
3100
        // calculate splitCount
3101
        $splitCount = count($valArr);
3102
        $max = (int)$this->stdWrapValue('max', $conf);
3103
        if ($max && $splitCount > $max) {
3104
            $splitCount = $max;
3105
        }
3106
        $min = (int)$this->stdWrapValue('min', $conf);
3107
        if ($min && $splitCount < $min) {
3108
            $splitCount = $min;
3109
        }
3110
        $wrap = (string)$this->stdWrapValue('wrap', $conf);
3111
        $cObjNumSplitConf = isset($conf['cObjNum.']) ? (string)$this->stdWrap($conf['cObjNum'], $conf['cObjNum.']) : (string)$conf['cObjNum'];
3112
        $splitArr = [];
3113
        if ($wrap !== '' || $cObjNumSplitConf !== '') {
3114
            $splitArr['wrap'] = $wrap;
3115
            $splitArr['cObjNum'] = $cObjNumSplitConf;
3116
            $splitArr = GeneralUtility::makeInstance(TypoScriptService::class)
3117
                ->explodeConfigurationForOptionSplit($splitArr, $splitCount);
3118
        }
3119
        $content = '';
3120
        for ($a = 0; $a < $splitCount; $a++) {
3121
            $this->getTypoScriptFrontendController()->register['SPLIT_COUNT'] = $a;
3122
            $value = '' . $valArr[$a];
3123
            $this->data[$this->currentValKey] = $value;
3124
            if ($splitArr[$a]['cObjNum']) {
3125
                $objName = (int)$splitArr[$a]['cObjNum'];
3126
                $value = isset($conf[$objName . '.'])
3127
                    ? $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...
3128
                    : $this->cObjGet($conf[$objName . '.'], $objName . '.');
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
3129
            }
3130
            $wrap = $this->stdWrapValue('wrap', $splitArr[$a]);
3131
            if ($wrap) {
3132
                $value = $this->wrap($value, $wrap);
3133
            }
3134
            $content .= $value;
3135
        }
3136
        return $content;
3137
    }
3138
3139
    /**
3140
     * Processes ordered replacements on content data.
3141
     *
3142
     * @param string $content The content to be processed
3143
     * @param array $configuration The TypoScript configuration for stdWrap.replacement
3144
     * @return string The processed content data
3145
     */
3146
    protected function replacement($content, array $configuration)
3147
    {
3148
        // Sorts actions in configuration by numeric index
3149
        ksort($configuration, SORT_NUMERIC);
3150
        foreach ($configuration as $index => $action) {
3151
            // Checks whether we have a valid action and a numeric key ending with a dot ("10.")
3152
            if (is_array($action) && substr($index, -1) === '.' && MathUtility::canBeInterpretedAsInteger(substr($index, 0, -1))) {
3153
                $content = $this->replacementSingle($content, $action);
3154
            }
3155
        }
3156
        return $content;
3157
    }
3158
3159
    /**
3160
     * Processes a single search/replace on content data.
3161
     *
3162
     * @param string $content The content to be processed
3163
     * @param array $configuration The TypoScript of the search/replace action to be processed
3164
     * @return string The processed content data
3165
     */
3166
    protected function replacementSingle($content, array $configuration)
3167
    {
3168
        if ((isset($configuration['search']) || isset($configuration['search.'])) && (isset($configuration['replace']) || isset($configuration['replace.']))) {
3169
            // Gets the strings
3170
            $search = $this->stdWrapValue('search', $configuration);
3171
            $replace = $this->stdWrapValue('replace', $configuration, null);
3172
3173
            // Determines whether regular expression shall be used
3174
            $useRegularExpression = (bool)$this->stdWrapValue('useRegExp', $configuration, false);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type integer|null|string expected by parameter $defaultValue of TYPO3\CMS\Frontend\Conte...enderer::stdWrapValue(). ( Ignorable by Annotation )

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

3174
            $useRegularExpression = (bool)$this->stdWrapValue('useRegExp', $configuration, /** @scrutinizer ignore-type */ false);
Loading history...
3175
3176
            // Determines whether replace-pattern uses option-split
3177
            $useOptionSplitReplace = (bool)$this->stdWrapValue('useOptionSplitReplace', $configuration, false);
3178
3179
            // Performs a replacement by preg_replace()
3180
            if ($useRegularExpression) {
3181
                // Get separator-character which precedes the string and separates search-string from the modifiers
3182
                $separator = $search[0];
3183
                $startModifiers = strrpos($search, $separator);
3184
                if ($separator !== false && $startModifiers > 0) {
3185
                    $modifiers = substr($search, $startModifiers + 1);
3186
                    // remove "e" (eval-modifier), which would otherwise allow to run arbitrary PHP-code
3187
                    $modifiers = str_replace('e', '', $modifiers);
3188
                    $search = substr($search, 0, $startModifiers + 1) . $modifiers;
3189
                }
3190
                if ($useOptionSplitReplace) {
3191
                    // init for replacement
3192
                    $splitCount = preg_match_all($search, $content, $matches);
3193
                    $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
3194
                    $replaceArray = $typoScriptService->explodeConfigurationForOptionSplit([$replace], $splitCount);
3195
                    $replaceCount = 0;
3196
3197
                    $replaceCallback = function ($match) use ($replaceArray, $search, &$replaceCount) {
3198
                        $replaceCount++;
3199
                        return preg_replace($search, $replaceArray[$replaceCount - 1][0], $match[0]);
3200
                    };
3201
                    $content = preg_replace_callback($search, $replaceCallback, $content);
3202
                } else {
3203
                    $content = preg_replace($search, $replace, $content);
3204
                }
3205
            } elseif ($useOptionSplitReplace) {
3206
                // turn search-string into a preg-pattern
3207
                $searchPreg = '#' . preg_quote($search, '#') . '#';
3208
3209
                // init for replacement
3210
                $splitCount = preg_match_all($searchPreg, $content, $matches);
3211
                $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
3212
                $replaceArray = $typoScriptService->explodeConfigurationForOptionSplit([$replace], $splitCount);
3213
                $replaceCount = 0;
3214
3215
                $replaceCallback = function () use ($replaceArray, &$replaceCount) {
3216
                    $replaceCount++;
3217
                    return $replaceArray[$replaceCount - 1][0];
3218
                };
3219
                $content = preg_replace_callback($searchPreg, $replaceCallback, $content);
3220
            } else {
3221
                $content = str_replace($search, $replace, $content);
3222
            }
3223
        }
3224
        return $content;
3225
    }
3226
3227
    /**
3228
     * Implements the "round" property of stdWrap
3229
     * This is a Wrapper function for PHP's rounding functions (round,ceil,floor), defaults to round()
3230
     *
3231
     * @param string $content Value to process
3232
     * @param array $conf TypoScript configuration for round
3233
     * @return string The formatted number
3234
     */
3235
    protected function round($content, array $conf = [])
3236
    {
3237
        $decimals = (int)$this->stdWrapValue('decimals', $conf, 0);
3238
        $type = $this->stdWrapValue('roundType', $conf);
3239
        $floatVal = (float)$content;
3240
        switch ($type) {
3241
            case 'ceil':
3242
                $content = ceil($floatVal);
3243
                break;
3244
            case 'floor':
3245
                $content = floor($floatVal);
3246
                break;
3247
            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...
3248
3249
            default:
3250
                $content = round($floatVal, $decimals);
3251
        }
3252
        return $content;
3253
    }
3254
3255
    /**
3256
     * Implements the stdWrap property "numberFormat"
3257
     * This is a Wrapper function for php's number_format()
3258
     *
3259
     * @param float $content Value to process
3260
     * @param array $conf TypoScript Configuration for numberFormat
3261
     * @return string The formatted number
3262
     */
3263
    public function numberFormat($content, $conf)
3264
    {
3265
        $decimals = (int)$this->stdWrapValue('decimals', $conf, 0);
3266
        $dec_point = $this->stdWrapValue('dec_point', $conf, '.');
3267
        $thousands_sep = $this->stdWrapValue('thousands_sep', $conf, ',');
3268
        return number_format((float)$content, $decimals, $dec_point, $thousands_sep);
3269
    }
3270
3271
    /**
3272
     * Implements the stdWrap property, "parseFunc".
3273
     * This is a function with a lot of interesting uses. In classic TypoScript this is used to process text
3274
     * from the bodytext field; This included highlighting of search words, changing http:// and mailto: prefixed strings into etc.
3275
     * It is still a very important function for processing of bodytext which is normally stored in the database
3276
     * in a format which is not fully ready to be outputted.
3277
     * This situation has not become better by having a RTE around...
3278
     *
3279
     * This function is actually just splitting the input content according to the configuration of "external blocks".
3280
     * This means that before the input string is actually "parsed" it will be splitted into the parts configured to BE parsed
3281
     * (while other parts/blocks should NOT be parsed).
3282
     * Therefore the actual processing of the parseFunc properties goes on in ->_parseFunc()
3283
     *
3284
     * @param string $theValue The value to process.
3285
     * @param array $conf TypoScript configuration for parseFunc
3286
     * @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!
3287
     * @return string The processed value
3288
     * @see _parseFunc()
3289
     */
3290
    public function parseFunc($theValue, $conf, $ref = '')
3291
    {
3292
        // Fetch / merge reference, if any
3293
        if ($ref) {
3294
            $temp_conf = [
3295
                'parseFunc' => $ref,
3296
                'parseFunc.' => $conf
3297
            ];
3298
            $temp_conf = $this->mergeTSRef($temp_conf, 'parseFunc');
3299
            $conf = $temp_conf['parseFunc.'];
3300
        }
3301
        // Process:
3302
        if ((string)($conf['externalBlocks'] ?? '') === '') {
3303
            return $this->_parseFunc($theValue, $conf);
3304
        }
3305
        $tags = strtolower(implode(',', GeneralUtility::trimExplode(',', $conf['externalBlocks'])));
3306
        $htmlParser = GeneralUtility::makeInstance(HtmlParser::class);
3307
        $parts = $htmlParser->splitIntoBlock($tags, $theValue);
3308
        foreach ($parts as $k => $v) {
3309
            if ($k % 2) {
3310
                // font:
3311
                $tagName = strtolower($htmlParser->getFirstTagName($v));
3312
                $cfg = $conf['externalBlocks.'][$tagName . '.'];
3313
                if ($cfg['stripNLprev'] || $cfg['stripNL']) {
3314
                    $parts[$k - 1] = preg_replace('/' . CR . '?' . LF . '[ ]*$/', '', $parts[$k - 1]);
3315
                }
3316
                if ($cfg['stripNLnext'] || $cfg['stripNL']) {
3317
                    $parts[$k + 1] = preg_replace('/^[ ]*' . CR . '?' . LF . '/', '', $parts[$k + 1]);
3318
                }
3319
            }
3320
        }
3321
        foreach ($parts as $k => $v) {
3322
            if ($k % 2) {
3323
                $tag = $htmlParser->getFirstTag($v);
3324
                $tagName = strtolower($htmlParser->getFirstTagName($v));
3325
                $cfg = $conf['externalBlocks.'][$tagName . '.'];
3326
                if ($cfg['callRecursive']) {
3327
                    $parts[$k] = $this->parseFunc($htmlParser->removeFirstAndLastTag($v), $conf);
3328
                    if (!$cfg['callRecursive.']['dontWrapSelf']) {
3329
                        if ($cfg['callRecursive.']['alternativeWrap']) {
3330
                            $parts[$k] = $this->wrap($parts[$k], $cfg['callRecursive.']['alternativeWrap']);
3331
                        } else {
3332
                            if (is_array($cfg['callRecursive.']['tagStdWrap.'])) {
3333
                                $tag = $this->stdWrap($tag, $cfg['callRecursive.']['tagStdWrap.']);
3334
                            }
3335
                            $parts[$k] = $tag . $parts[$k] . '</' . $tagName . '>';
3336
                        }
3337
                    }
3338
                } elseif ($cfg['HTMLtableCells']) {
3339
                    $rowParts = $htmlParser->splitIntoBlock('tr', $parts[$k]);
3340
                    foreach ($rowParts as $kk => $vv) {
3341
                        if ($kk % 2) {
3342
                            $colParts = $htmlParser->splitIntoBlock('td,th', $vv);
3343
                            $cc = 0;
3344
                            foreach ($colParts as $kkk => $vvv) {
3345
                                if ($kkk % 2) {
3346
                                    $cc++;
3347
                                    $tag = $htmlParser->getFirstTag($vvv);
3348
                                    $tagName = strtolower($htmlParser->getFirstTagName($vvv));
3349
                                    $colParts[$kkk] = $htmlParser->removeFirstAndLastTag($vvv);
3350
                                    if ($cfg['HTMLtableCells.'][$cc . '.']['callRecursive'] || !isset($cfg['HTMLtableCells.'][$cc . '.']['callRecursive']) && $cfg['HTMLtableCells.']['default.']['callRecursive']) {
3351
                                        if ($cfg['HTMLtableCells.']['addChr10BetweenParagraphs']) {
3352
                                            $colParts[$kkk] = str_replace('</p><p>', '</p>' . LF . '<p>', $colParts[$kkk]);
3353
                                        }
3354
                                        $colParts[$kkk] = $this->parseFunc($colParts[$kkk], $conf);
3355
                                    }
3356
                                    $tagStdWrap = is_array($cfg['HTMLtableCells.'][$cc . '.']['tagStdWrap.'])
3357
                                        ? $cfg['HTMLtableCells.'][$cc . '.']['tagStdWrap.']
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
3358
                                        : $cfg['HTMLtableCells.']['default.']['tagStdWrap.'];
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
3359
                                    if (is_array($tagStdWrap)) {
3360
                                        $tag = $this->stdWrap($tag, $tagStdWrap);
3361
                                    }
3362
                                    $stdWrap = is_array($cfg['HTMLtableCells.'][$cc . '.']['stdWrap.'])
3363
                                        ? $cfg['HTMLtableCells.'][$cc . '.']['stdWrap.']
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
3364
                                        : $cfg['HTMLtableCells.']['default.']['stdWrap.'];
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
3365
                                    if (is_array($stdWrap)) {
3366
                                        $colParts[$kkk] = $this->stdWrap($colParts[$kkk], $stdWrap);
3367
                                    }
3368
                                    $colParts[$kkk] = $tag . $colParts[$kkk] . '</' . $tagName . '>';
3369
                                }
3370
                            }
3371
                            $rowParts[$kk] = implode('', $colParts);
3372
                        }
3373
                    }
3374
                    $parts[$k] = implode('', $rowParts);
3375
                }
3376
                if (is_array($cfg['stdWrap.'])) {
3377
                    $parts[$k] = $this->stdWrap($parts[$k], $cfg['stdWrap.']);
3378
                }
3379
            } else {
3380
                $parts[$k] = $this->_parseFunc($parts[$k], $conf);
3381
            }
3382
        }
3383
        return implode('', $parts);
3384
    }
3385
3386
    /**
3387
     * Helper function for parseFunc()
3388
     *
3389
     * @param string $theValue The value to process.
3390
     * @param array $conf TypoScript configuration for parseFunc
3391
     * @return string The processed value
3392
     * @internal
3393
     * @see parseFunc()
3394
     */
3395
    public function _parseFunc($theValue, $conf)
3396
    {
3397
        if (!empty($conf['if.']) && !$this->checkIf($conf['if.'])) {
3398
            return $theValue;
3399
        }
3400
        // Indicates that the data is from within a tag.
3401
        $inside = false;
3402
        // Pointer to the total string position
3403
        $pointer = 0;
3404
        // Loaded with the current typo-tag if any.
3405
        $currentTag = null;
3406
        $stripNL = 0;
3407
        $contentAccum = [];
3408
        $contentAccumP = 0;
3409
        $allowTags = strtolower(str_replace(' ', '', $conf['allowTags'] ?? ''));
3410
        $denyTags = strtolower(str_replace(' ', '', $conf['denyTags'] ?? ''));
3411
        $totalLen = strlen($theValue);
3412
        do {
3413
            if (!$inside) {
3414
                if ($currentTag === null) {
3415
                    // These operations should only be performed on code outside the typotags...
3416
                    // data: this checks that we enter tags ONLY if the first char in the tag is alphanumeric OR '/'
3417
                    $len_p = 0;
3418
                    $c = 100;
3419
                    do {
3420
                        $len = strcspn(substr($theValue, $pointer + $len_p), '<');
3421
                        $len_p += $len + 1;
3422
                        $endChar = ord(strtolower(substr($theValue, $pointer + $len_p, 1)));
3423
                        $c--;
3424
                    } while ($c > 0 && $endChar && ($endChar < 97 || $endChar > 122) && $endChar != 47);
3425
                    $len = $len_p - 1;
3426
                } else {
3427
                    $len = $this->getContentLengthOfCurrentTag($theValue, $pointer, $currentTag[0]);
3428
                }
3429
                // $data is the content until the next <tag-start or end is detected.
3430
                // In case of a currentTag set, this would mean all data between the start- and end-tags
3431
                $data = substr($theValue, $pointer, $len);
3432
                if ($data !== false) {
3433
                    if ($stripNL) {
3434
                        // If the previous tag was set to strip NewLines in the beginning of the next data-chunk.
3435
                        $data = preg_replace('/^[ ]*' . CR . '?' . LF . '/', '', $data);
3436
                    }
3437
                    // These operations should only be performed on code outside the tags...
3438
                    if (!is_array($currentTag)) {
3439
                        // Constants
3440
                        $tsfe = $this->getTypoScriptFrontendController();
3441
                        $tmpConstants = $tsfe->tmpl->setup['constants.'] ?? null;
3442
                        if (!empty($conf['constants']) && is_array($tmpConstants)) {
3443
                            foreach ($tmpConstants as $key => $val) {
3444
                                if (is_string($val)) {
3445
                                    $data = str_replace('###' . $key . '###', $val, $data);
3446
                                }
3447
                            }
3448
                        }
3449
                        // Short
3450
                        if (isset($conf['short.']) && is_array($conf['short.'])) {
3451
                            $shortWords = $conf['short.'];
3452
                            krsort($shortWords);
3453
                            foreach ($shortWords as $key => $val) {
3454
                                if (is_string($val)) {
3455
                                    $data = str_replace($key, $val, $data);
3456
                                }
3457
                            }
3458
                        }
3459
                        // stdWrap
3460
                        if (isset($conf['plainTextStdWrap.']) && is_array($conf['plainTextStdWrap.'])) {
3461
                            $data = $this->stdWrap($data, $conf['plainTextStdWrap.']);
3462
                        }
3463
                        // userFunc
3464
                        if ($conf['userFunc'] ?? false) {
3465
                            $data = $this->callUserFunction($conf['userFunc'], $conf['userFunc.'], $data);
3466
                        }
3467
                        // Makelinks: (Before search-words as we need the links to be generated when searchwords go on...!)
3468
                        if ($conf['makelinks'] ?? false) {
3469
                            $data = $this->http_makelinks($data, $conf['makelinks.']['http.']);
3470
                            $data = $this->mailto_makelinks($data, $conf['makelinks.']['mailto.'] ?? []);
3471
                        }
3472
                        // Search Words:
3473
                        if ($tsfe->no_cache && $conf['sword'] && is_array($tsfe->sWordList) && $tsfe->sWordRegEx) {
3474
                            $newstring = '';
3475
                            do {
3476
                                $pregSplitMode = 'i';
3477
                                if (isset($tsfe->config['config']['sword_noMixedCase']) && !empty($tsfe->config['config']['sword_noMixedCase'])) {
3478
                                    $pregSplitMode = '';
3479
                                }
3480
                                $pieces = preg_split('/' . $tsfe->sWordRegEx . '/' . $pregSplitMode, $data, 2);
3481
                                $newstring .= $pieces[0];
3482
                                $match_len = strlen($data) - (strlen($pieces[0]) + strlen($pieces[1]));
3483
                                $inTag = false;
3484
                                if (strpos($pieces[0], '<') !== false || strpos($pieces[0], '>') !== false) {
3485
                                    // Returns TRUE, if a '<' is closer to the string-end than '>'.
3486
                                    // This is the case if we're INSIDE a tag (that could have been
3487
                                    // made by makelinks...) and we must secure, that the inside of a tag is
3488
                                    // not marked up.
3489
                                    $inTag = strrpos($pieces[0], '<') > strrpos($pieces[0], '>');
3490
                                }
3491
                                // The searchword:
3492
                                $match = substr($data, strlen($pieces[0]), $match_len);
3493
                                if (trim($match) && strlen($match) > 1 && !$inTag) {
3494
                                    $match = $this->wrap($match, $conf['sword']);
3495
                                }
3496
                                // Concatenate the Search Word again.
3497
                                $newstring .= $match;
3498
                                $data = $pieces[1];
3499
                            } while ($pieces[1]);
3500
                            $data = $newstring;
3501
                        }
3502
                    }
3503
                    // Search for tags to process in current data and
3504
                    // call this method recursively if found
3505
                    if (strpos($data, '<') !== false && isset($conf['tags.']) && is_array($conf['tags.'])) {
3506
                        foreach ($conf['tags.'] as $tag => $tagConfig) {
3507
                            // only match tag `a` in `<a href"...">` but not in `<abbr>`
3508
                            if (preg_match('#<' . $tag . '[\s/>]#', $data)) {
3509
                                $data = $this->_parseFunc($data, $conf);
3510
                                break;
3511
                            }
3512
                        }
3513
                    }
3514
                    $contentAccum[$contentAccumP] = isset($contentAccum[$contentAccumP])
3515
                        ? $contentAccum[$contentAccumP] . $data
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
3516
                        : $data;
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
3517
                }
3518
                $inside = true;
3519
            } else {
3520
                // tags
3521
                $len = strcspn(substr($theValue, $pointer), '>') + 1;
3522
                $data = substr($theValue, $pointer, $len);
3523
                if (StringUtility::endsWith($data, '/>') && strpos($data, '<link ') !== 0) {
3524
                    $tagContent = substr($data, 1, -2);
3525
                } else {
3526
                    $tagContent = substr($data, 1, -1);
3527
                }
3528
                $tag = explode(' ', trim($tagContent), 2);
3529
                $tag[0] = strtolower($tag[0]);
3530
                // end tag like </li>
3531
                if ($tag[0][0] === '/') {
3532
                    $tag[0] = substr($tag[0], 1);
3533
                    $tag['out'] = 1;
3534
                }
3535
                if ($conf['tags.'][$tag[0]] ?? false) {
3536
                    $treated = false;
3537
                    $stripNL = false;
3538
                    // in-tag
3539
                    if (!$currentTag && (!isset($tag['out']) || !$tag['out'])) {
3540
                        // $currentTag (array!) is the tag we are currently processing
3541
                        $currentTag = $tag;
3542
                        $contentAccumP++;
3543
                        $treated = true;
3544
                        // in-out-tag: img and other empty tags
3545
                        if (preg_match('/^(area|base|br|col|hr|img|input|meta|param)$/i', $tag[0])) {
3546
                            $tag['out'] = 1;
3547
                        }
3548
                    }
3549
                    // out-tag
3550
                    if ($currentTag[0] === $tag[0] && isset($tag['out']) && $tag['out']) {
3551
                        $theName = $conf['tags.'][$tag[0]];
3552
                        $theConf = $conf['tags.'][$tag[0] . '.'];
3553
                        // This flag indicates, that NL- (13-10-chars) should be stripped first and last.
3554
                        $stripNL = (bool)($theConf['stripNL'] ?? false);
3555
                        // This flag indicates, that this TypoTag section should NOT be included in the nonTypoTag content.
3556
                        $breakOut = (bool)($theConf['breakoutTypoTagContent'] ?? false);
3557
                        $this->parameters = [];
3558
                        if (isset($currentTag[1])) {
3559
                            // decode HTML entities in attributes, since they're processed
3560
                            $params = GeneralUtility::get_tag_attributes($currentTag[1], true);
3561
                            if (is_array($params)) {
3562
                                foreach ($params as $option => $val) {
3563
                                    // contains non-encoded values
3564
                                    $this->parameters[strtolower($option)] = $val;
3565
                                }
3566
                            }
3567
                            $this->parameters['allParams'] = trim($currentTag[1]);
3568
                        }
3569
                        // Removes NL in the beginning and end of the tag-content AND at the end of the currentTagBuffer.
3570
                        // $stripNL depends on the configuration of the current tag
3571
                        if ($stripNL) {
3572
                            $contentAccum[$contentAccumP - 1] = preg_replace('/' . CR . '?' . LF . '[ ]*$/', '', $contentAccum[$contentAccumP - 1]);
3573
                            $contentAccum[$contentAccumP] = preg_replace('/^[ ]*' . CR . '?' . LF . '/', '', $contentAccum[$contentAccumP]);
3574
                            $contentAccum[$contentAccumP] = preg_replace('/' . CR . '?' . LF . '[ ]*$/', '', $contentAccum[$contentAccumP]);
3575
                        }
3576
                        $this->data[$this->currentValKey] = $contentAccum[$contentAccumP];
3577
                        $newInput = $this->cObjGetSingle($theName, $theConf, '/parseFunc/.tags.' . $tag[0]);
3578
                        // fetch the content object
3579
                        $contentAccum[$contentAccumP] = $newInput;
3580
                        $contentAccumP++;
3581
                        // If the TypoTag section
3582
                        if (!$breakOut) {
3583
                            if (!isset($contentAccum[$contentAccumP - 2])) {
3584
                                $contentAccum[$contentAccumP - 2] = '';
3585
                            }
3586
                            $contentAccum[$contentAccumP - 2] .= ($contentAccum[$contentAccumP - 1] ?? '') . ($contentAccum[$contentAccumP] ?? '');
3587
                            unset($contentAccum[$contentAccumP]);
3588
                            unset($contentAccum[$contentAccumP - 1]);
3589
                            $contentAccumP -= 2;
3590
                        }
3591
                        $currentTag = null;
3592
                        $treated = true;
3593
                    }
3594
                    // other tags
3595
                    if (!$treated) {
3596
                        $contentAccum[$contentAccumP] .= $data;
3597
                    }
3598
                } else {
3599
                    // If a tag was not a typo tag, then it is just added to the content
3600
                    $stripNL = false;
3601
                    if (GeneralUtility::inList($allowTags, $tag[0]) ||
3602
                        ($denyTags !== '*' && !GeneralUtility::inList($denyTags, $tag[0]))) {
3603
                        $contentAccum[$contentAccumP] = isset($contentAccum[$contentAccumP])
3604
                            ? $contentAccum[$contentAccumP] . $data
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
3605
                            : $data;
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
3606
                    } else {
3607
                        $contentAccum[$contentAccumP] = isset($contentAccum[$contentAccumP])
3608
                            ? $contentAccum[$contentAccumP] . htmlspecialchars($data)
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
3609
                            : htmlspecialchars($data);
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
3610
                    }
3611
                }
3612
                $inside = false;
3613
            }
3614
            $pointer += $len;
3615
        } while ($pointer < $totalLen);
3616
        // Parsing nonTypoTag content (all even keys):
3617
        reset($contentAccum);
3618
        $contentAccumCount = count($contentAccum);
3619
        for ($a = 0; $a < $contentAccumCount; $a++) {
3620
            if ($a % 2 != 1) {
3621
                // stdWrap
3622
                if (isset($conf['nonTypoTagStdWrap.']) && is_array($conf['nonTypoTagStdWrap.'])) {
3623
                    $contentAccum[$a] = $this->stdWrap($contentAccum[$a], $conf['nonTypoTagStdWrap.']);
3624
                }
3625
                // userFunc
3626
                if (!empty($conf['nonTypoTagUserFunc'])) {
3627
                    $contentAccum[$a] = $this->callUserFunction($conf['nonTypoTagUserFunc'], $conf['nonTypoTagUserFunc.'], $contentAccum[$a]);
3628
                }
3629
            }
3630
        }
3631
        return implode('', $contentAccum);
3632
    }
3633
3634
    /**
3635
     * Lets you split the content by LF and process each line independently. Used to format content made with the RTE.
3636
     *
3637
     * @param string $theValue The input value
3638
     * @param array $conf TypoScript options
3639
     * @return string The processed input value being returned; Splitted lines imploded by LF again.
3640
     * @internal
3641
     */
3642
    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...
3643
    {
3644
        if ((string)$theValue === '') {
3645
            return '';
3646
        }
3647
        $lParts = explode(LF, $theValue);
3648
3649
        // When the last element is an empty linebreak we need to remove it, otherwise we will have a duplicate empty line.
3650
        $lastPartIndex = count($lParts) - 1;
3651
        if ($lParts[$lastPartIndex] === '' && trim($lParts[$lastPartIndex - 1], CR) === '') {
3652
            array_pop($lParts);
3653
        }
3654
3655
        $encapTags = GeneralUtility::trimExplode(',', strtolower($conf['encapsTagList']), true);
3656
        $nonWrappedTag = $conf['nonWrappedTag'];
3657
        $defaultAlign = trim($this->stdWrapValue('defaultAlign', $conf));
3658
3659
        $str_content = '';
3660
        foreach ($lParts as $k => $l) {
3661
            $sameBeginEnd = 0;
3662
            $emptyTag = false;
3663
            $l = trim($l);
3664
            $attrib = [];
3665
            $nonWrapped = false;
3666
            $tagName = '';
3667
            if (isset($l[0]) && $l[0] === '<' && substr($l, -1) === '>') {
3668
                $fwParts = explode('>', substr($l, 1), 2);
3669
                [$tagName] = explode(' ', $fwParts[0], 2);
3670
                if (!$fwParts[1]) {
3671
                    if (substr($tagName, -1) === '/') {
3672
                        $tagName = substr($tagName, 0, -1);
3673
                    }
3674
                    if (substr($fwParts[0], -1) === '/') {
3675
                        $sameBeginEnd = 1;
3676
                        $emptyTag = true;
3677
                        // decode HTML entities, they're encoded later again
3678
                        $attrib = GeneralUtility::get_tag_attributes('<' . substr($fwParts[0], 0, -1) . '>', true);
3679
                    }
3680
                } else {
3681
                    $backParts = GeneralUtility::revExplode('<', substr($fwParts[1], 0, -1), 2);
3682
                    // decode HTML entities, they're encoded later again
3683
                    $attrib = GeneralUtility::get_tag_attributes('<' . $fwParts[0] . '>', true);
3684
                    $str_content = $backParts[0];
3685
                    $sameBeginEnd = substr(strtolower($backParts[1]), 1, strlen($tagName)) === strtolower($tagName);
3686
                }
3687
            }
3688
            if ($sameBeginEnd && in_array(strtolower($tagName), $encapTags)) {
3689
                $uTagName = strtoupper($tagName);
3690
                $uTagName = strtoupper($conf['remapTag.'][$uTagName] ?? $uTagName);
3691
            } else {
3692
                $uTagName = strtoupper($nonWrappedTag);
3693
                // The line will be wrapped: $uTagName should not be an empty tag
3694
                $emptyTag = false;
3695
                $str_content = $lParts[$k];
3696
                $nonWrapped = true;
3697
                $attrib = [];
3698
            }
3699
            // Wrapping all inner-content:
3700
            if (is_array($conf['innerStdWrap_all.'])) {
3701
                $str_content = $this->stdWrap($str_content, $conf['innerStdWrap_all.']);
3702
            }
3703
            if ($uTagName) {
3704
                // Setting common attributes
3705
                if (isset($conf['addAttributes.'][$uTagName . '.']) && is_array($conf['addAttributes.'][$uTagName . '.'])) {
3706
                    foreach ($conf['addAttributes.'][$uTagName . '.'] as $kk => $vv) {
3707
                        if (!is_array($vv)) {
3708
                            if ((string)$conf['addAttributes.'][$uTagName . '.'][$kk . '.']['setOnly'] === 'blank') {
3709
                                if ((string)($attrib[$kk] ?? '') === '') {
3710
                                    $attrib[$kk] = $vv;
3711
                                }
3712
                            } elseif ((string)$conf['addAttributes.'][$uTagName . '.'][$kk . '.']['setOnly'] === 'exists') {
3713
                                if (!isset($attrib[$kk])) {
3714
                                    $attrib[$kk] = $vv;
3715
                                }
3716
                            } else {
3717
                                $attrib[$kk] = $vv;
3718
                            }
3719
                        }
3720
                    }
3721
                }
3722
                // Wrapping all inner-content:
3723
                if (isset($conf['encapsLinesStdWrap.'][$uTagName . '.']) && is_array($conf['encapsLinesStdWrap.'][$uTagName . '.'])) {
3724
                    $str_content = $this->stdWrap($str_content, $conf['encapsLinesStdWrap.'][$uTagName . '.']);
3725
                }
3726
                // Default align
3727
                if ((!isset($attrib['align']) || !$attrib['align']) && $defaultAlign) {
3728
                    $attrib['align'] = $defaultAlign;
3729
                }
3730
                // implode (insecure) attributes, that's why `htmlspecialchars` is used here
3731
                $params = GeneralUtility::implodeAttributes($attrib, true);
3732
                if (!isset($conf['removeWrapping']) || !$conf['removeWrapping'] || ($emptyTag && $conf['removeWrapping.']['keepSingleTag'])) {
3733
                    $selfClosingTagList = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
3734
                    if ($emptyTag && in_array(strtolower($uTagName), $selfClosingTagList, true)) {
3735
                        $str_content = '<' . strtolower($uTagName) . (trim($params) ? ' ' . trim($params) : '') . ' />';
3736
                    } else {
3737
                        $str_content = '<' . strtolower($uTagName) . (trim($params) ? ' ' . trim($params) : '') . '>' . $str_content . '</' . strtolower($uTagName) . '>';
3738
                    }
3739
                }
3740
            }
3741
            if ($nonWrapped && isset($conf['wrapNonWrappedLines']) && $conf['wrapNonWrappedLines']) {
3742
                $str_content = $this->wrap($str_content, $conf['wrapNonWrappedLines']);
3743
            }
3744
            $lParts[$k] = $str_content;
3745
        }
3746
        return implode(LF, $lParts);
3747
    }
3748
3749
    /**
3750
     * Finds URLS in text and makes it to a real link.
3751
     * Will find all strings prefixed with "http://" and "https://" in the $data string and make them into a link,
3752
     * linking to the URL we should have found.
3753
     *
3754
     * @param string $data The string in which to search for "http://
3755
     * @param array $conf Configuration for makeLinks, see link
3756
     * @return string The processed input string, being returned.
3757
     * @see _parseFunc()
3758
     */
3759
    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...
3760
    {
3761
        $parts = [];
3762
        $aTagParams = $this->getATagParams($conf);
3763
        $textstr = '';
3764
        foreach (['http://', 'https://'] as $scheme) {
3765
            $textpieces = explode($scheme, $data);
3766
            $pieces = count($textpieces);
3767
            $textstr = $textpieces[0];
3768
            for ($i = 1; $i < $pieces; $i++) {
3769
                $len = strcspn($textpieces[$i], chr(32) . "\t" . CRLF);
3770
                if (trim(substr($textstr, -1)) === '' && $len) {
3771
                    $lastChar = substr($textpieces[$i], $len - 1, 1);
3772
                    if (!preg_match('/[A-Za-z0-9\\/#_-]/', $lastChar)) {
3773
                        $len--;
3774
                    }
3775
                    // Included '\/' 3/12
3776
                    $parts[0] = substr($textpieces[$i], 0, $len);
3777
                    $parts[1] = substr($textpieces[$i], $len);
3778
                    $keep = $conf['keep'];
3779
                    $linkParts = parse_url($scheme . $parts[0]);
3780
                    $linktxt = '';
3781
                    if (strpos($keep, 'scheme') !== false) {
3782
                        $linktxt = $scheme;
3783
                    }
3784
                    $linktxt .= $linkParts['host'];
3785
                    if (strpos($keep, 'path') !== false) {
3786
                        $linktxt .= $linkParts['path'];
3787
                        // Added $linkParts['query'] 3/12
3788
                        if (strpos($keep, 'query') !== false && $linkParts['query']) {
3789
                            $linktxt .= '?' . $linkParts['query'];
3790
                        } elseif ($linkParts['path'] === '/') {
3791
                            $linktxt = substr($linktxt, 0, -1);
3792
                        }
3793
                    }
3794
                    $target = $this->stdWrapValue('extTarget', $conf, $this->getTypoScriptFrontendController()->extTarget);
3795
3796
                    // check for jump URLs or similar
3797
                    $linkUrl = $this->processUrl(UrlProcessorInterface::CONTEXT_COMMON, $scheme . $parts[0], $conf);
3798
3799
                    $res = '<a href="' . htmlspecialchars($linkUrl) . '"'
3800
                        . ($target !== '' ? ' target="' . htmlspecialchars($target) . '"' : '')
3801
                        . $aTagParams . '>';
3802
3803
                    $wrap = $this->stdWrapValue('wrap', $conf);
3804
                    if ((string)$conf['ATagBeforeWrap'] !== '') {
3805
                        $res = $res . $this->wrap($linktxt, $wrap) . '</a>';
3806
                    } else {
3807
                        $res = $this->wrap($res . $linktxt . '</a>', $wrap);
3808
                    }
3809
                    $textstr .= $res . $parts[1];
3810
                } else {
3811
                    $textstr .= $scheme . $textpieces[$i];
3812
                }
3813
            }
3814
            $data = $textstr;
3815
        }
3816
        return $textstr;
3817
    }
3818
3819
    /**
3820
     * Will find all strings prefixed with "mailto:" in the $data string and make them into a link,
3821
     * linking to the email address they point to.
3822
     *
3823
     * @param string $data The string in which to search for "mailto:
3824
     * @param array $conf Configuration for makeLinks, see link
3825
     * @return string The processed input string, being returned.
3826
     * @see _parseFunc()
3827
     */
3828
    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...
3829
    {
3830
        $parts = [];
3831
        // http-split
3832
        $aTagParams = $this->getATagParams($conf);
3833
        $textpieces = explode('mailto:', $data);
3834
        $pieces = count($textpieces);
3835
        $textstr = $textpieces[0];
3836
        $tsfe = $this->getTypoScriptFrontendController();
3837
        for ($i = 1; $i < $pieces; $i++) {
3838
            $len = strcspn($textpieces[$i], chr(32) . "\t" . CRLF);
3839
            if (trim(substr($textstr, -1)) === '' && $len) {
3840
                $lastChar = substr($textpieces[$i], $len - 1, 1);
3841
                if (!preg_match('/[A-Za-z0-9]/', $lastChar)) {
3842
                    $len--;
3843
                }
3844
                $parts[0] = substr($textpieces[$i], 0, $len);
3845
                $parts[1] = substr($textpieces[$i], $len);
3846
                $linktxt = preg_replace('/\\?.*/', '', $parts[0]);
3847
                [$mailToUrl, $linktxt] = $this->getMailTo($parts[0], $linktxt);
3848
                $mailToUrl = $tsfe->spamProtectEmailAddresses === 'ascii' ? $mailToUrl : htmlspecialchars($mailToUrl);
3849
                $res = '<a href="' . $mailToUrl . '"' . $aTagParams . '>';
3850
                $wrap = $this->stdWrapValue('wrap', $conf);
3851
                if ((string)$conf['ATagBeforeWrap'] !== '') {
3852
                    $res = $res . $this->wrap($linktxt, $wrap) . '</a>';
3853
                } else {
3854
                    $res = $this->wrap($res . $linktxt . '</a>', $wrap);
3855
                }
3856
                $textstr .= $res . $parts[1];
3857
            } else {
3858
                $textstr .= 'mailto:' . $textpieces[$i];
3859
            }
3860
        }
3861
        return $textstr;
3862
    }
3863
3864
    /**
3865
     * Creates and returns a TypoScript "imgResource".
3866
     * The value ($file) can either be a file reference (TypoScript resource) or the string "GIFBUILDER".
3867
     * In the first case a current image is returned, possibly scaled down or otherwise processed.
3868
     * In the latter case a GIFBUILDER image is returned; This means an image is made by TYPO3 from layers of elements as GIFBUILDER defines.
3869
     * In the function IMG_RESOURCE() this function is called like $this->getImgResource($conf['file'], $conf['file.']);
3870
     *
3871
     * Structure of the returned info array:
3872
     *  0 => width
3873
     *  1 => height
3874
     *  2 => file extension
3875
     *  3 => file name
3876
     *  origFile => original file name
3877
     *  origFile_mtime => original file mtime
3878
     *  -- only available if processed via FAL: --
3879
     *  originalFile => original file object
3880
     *  processedFile => processed file object
3881
     *  fileCacheHash => checksum of processed file
3882
     *
3883
     * @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.
3884
     * @param array $fileArray TypoScript properties for the imgResource type
3885
     * @return array|null Returns info-array
3886
     * @see cImage()
3887
     * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder
3888
     */
3889
    public function getImgResource($file, $fileArray)
3890
    {
3891
        $importedFile = null;
3892
        if (empty($file) && empty($fileArray)) {
3893
            return null;
3894
        }
3895
        if (!is_array($fileArray)) {
0 ignored issues
show
introduced by
The condition is_array($fileArray) is always true.
Loading history...
3896
            $fileArray = (array)$fileArray;
3897
        }
3898
        $imageResource = null;
3899
        if ($file === 'GIFBUILDER') {
3900
            $gifCreator = GeneralUtility::makeInstance(GifBuilder::class);
3901
            $theImage = '';
3902
            if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib']) {
3903
                $gifCreator->start($fileArray, $this->data);
3904
                $theImage = $gifCreator->gifBuild();
3905
            }
3906
            $imageResource = $gifCreator->getImageDimensions($theImage);
3907
            $imageResource['origFile'] = $theImage;
3908
        } else {
3909
            if ($file instanceof File) {
3910
                $fileObject = $file;
3911
            } elseif ($file instanceof FileReference) {
3912
                $fileObject = $file->getOriginalFile();
3913
            } else {
3914
                try {
3915
                    if (isset($fileArray['import.']) && $fileArray['import.']) {
3916
                        $importedFile = trim($this->stdWrap('', $fileArray['import.']));
3917
                        if (!empty($importedFile)) {
3918
                            $file = $importedFile;
3919
                        }
3920
                    }
3921
3922
                    if (MathUtility::canBeInterpretedAsInteger($file)) {
3923
                        $treatIdAsReference = $this->stdWrapValue('treatIdAsReference', $fileArray);
3924
                        if (!empty($treatIdAsReference)) {
3925
                            $file = $this->getResourceFactory()->getFileReferenceObject($file);
3926
                            $fileObject = $file->getOriginalFile();
3927
                        } else {
3928
                            $fileObject = $this->getResourceFactory()->getFileObject($file);
3929
                        }
3930
                    } elseif (preg_match('/^(0|[1-9][0-9]*):/', $file)) { // combined identifier
3931
                        $fileObject = $this->getResourceFactory()->retrieveFileOrFolderObject($file);
3932
                    } else {
3933
                        if (isset($importedFile) && !empty($importedFile) && !empty($fileArray['import'])) {
3934
                            $file = $fileArray['import'] . $file;
3935
                        }
3936
                        $fileObject = $this->getResourceFactory()->retrieveFileOrFolderObject($file);
3937
                    }
3938
                } catch (Exception $exception) {
3939
                    $this->logger->warning('The image "' . $file . '" could not be found and won\'t be included in frontend output', ['exception' => $exception]);
3940
                    return null;
3941
                }
3942
            }
3943
            if ($fileObject instanceof File) {
3944
                $processingConfiguration = [];
3945
                $processingConfiguration['width'] = $this->stdWrapValue('width', $fileArray);
3946
                $processingConfiguration['height'] = $this->stdWrapValue('height', $fileArray);
3947
                $processingConfiguration['fileExtension'] = $this->stdWrapValue('ext', $fileArray);
3948
                $processingConfiguration['maxWidth'] = (int)$this->stdWrapValue('maxW', $fileArray);
3949
                $processingConfiguration['maxHeight'] = (int)$this->stdWrapValue('maxH', $fileArray);
3950
                $processingConfiguration['minWidth'] = (int)$this->stdWrapValue('minW', $fileArray);
3951
                $processingConfiguration['minHeight'] = (int)$this->stdWrapValue('minH', $fileArray);
3952
                $processingConfiguration['noScale'] = $this->stdWrapValue('noScale', $fileArray);
3953
                $processingConfiguration['additionalParameters'] = $this->stdWrapValue('params', $fileArray);
3954
                $processingConfiguration['frame'] = (int)$this->stdWrapValue('frame', $fileArray);
3955
                if ($file instanceof FileReference) {
3956
                    $processingConfiguration['crop'] = $this->getCropAreaFromFileReference($file, $fileArray);
3957
                } else {
3958
                    $processingConfiguration['crop'] = $this->getCropAreaFromFromTypoScriptSettings($fileObject, $fileArray);
3959
                }
3960
3961
                // Possibility to cancel/force profile extraction
3962
                // see $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand']
3963
                if (isset($fileArray['stripProfile'])) {
3964
                    $processingConfiguration['stripProfile'] = $fileArray['stripProfile'];
3965
                }
3966
                // Check if we can handle this type of file for editing
3967
                if ($fileObject->isImage()) {
3968
                    $maskArray = $fileArray['m.'];
3969
                    // Must render mask images and include in hash-calculating
3970
                    // - otherwise we cannot be sure the filename is unique for the setup!
3971
                    if (is_array($maskArray)) {
3972
                        $mask = $this->getImgResource($maskArray['mask'], $maskArray['mask.']);
3973
                        $bgImg = $this->getImgResource($maskArray['bgImg'], $maskArray['bgImg.']);
3974
                        $bottomImg = $this->getImgResource($maskArray['bottomImg'], $maskArray['bottomImg.']);
3975
                        $bottomImg_mask = $this->getImgResource($maskArray['bottomImg_mask'], $maskArray['bottomImg_mask.']);
3976
3977
                        $processingConfiguration['maskImages']['maskImage'] = $mask['processedFile'];
3978
                        $processingConfiguration['maskImages']['backgroundImage'] = $bgImg['processedFile'];
3979
                        $processingConfiguration['maskImages']['maskBottomImage'] = $bottomImg['processedFile'];
3980
                        $processingConfiguration['maskImages']['maskBottomImageMask'] = $bottomImg_mask['processedFile'];
3981
                    }
3982
                    $processedFileObject = $fileObject->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $processingConfiguration);
3983
                    if ($processedFileObject->isProcessed()) {
3984
                        $imageResource = [
3985
                            0 => (int)$processedFileObject->getProperty('width'),
3986
                            1 => (int)$processedFileObject->getProperty('height'),
3987
                            2 => $processedFileObject->getExtension(),
3988
                            3 => $processedFileObject->getPublicUrl(),
3989
                            'origFile' => $fileObject->getPublicUrl(),
3990
                            'origFile_mtime' => $fileObject->getModificationTime(),
3991
                            // This is needed by \TYPO3\CMS\Frontend\Imaging\GifBuilder,
3992
                            // in order for the setup-array to create a unique filename hash.
3993
                            'originalFile' => $fileObject,
3994
                            'processedFile' => $processedFileObject
3995
                        ];
3996
                    }
3997
                }
3998
            }
3999
        }
4000
        // If image was processed by GIFBUILDER:
4001
        // ($imageResource indicates that it was processed the regular way)
4002
        if (!isset($imageResource)) {
4003
            try {
4004
                $theImage = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize((string)$file);
4005
                $info = GeneralUtility::makeInstance(GifBuilder::class)->imageMagickConvert($theImage, 'WEB');
4006
                $info['origFile'] = $theImage;
4007
                // This is needed by \TYPO3\CMS\Frontend\Imaging\GifBuilder, ln 100ff in order for the setup-array to create a unique filename hash.
4008
                $info['origFile_mtime'] = @filemtime($theImage);
4009
                $imageResource = $info;
4010
            } catch (Exception $e) {
4011
                // do nothing in case the file path is invalid
4012
            }
4013
        }
4014
        // Hook 'getImgResource': Post-processing of image resources
4015
        if (isset($imageResource)) {
4016
            /** @var ContentObjectGetImageResourceHookInterface $hookObject */
4017
            foreach ($this->getGetImgResourceHookObjects() as $hookObject) {
4018
                $imageResource = $hookObject->getImgResourcePostProcess($file, (array)$fileArray, $imageResource, $this);
4019
            }
4020
        }
4021
        return $imageResource;
4022
    }
4023
4024
    /**
4025
     * Returns an ImageManipulation\Area object for the given cropVariant (or 'default')
4026
     * or null if the crop settings or crop area is empty.
4027
     *
4028
     * The cropArea from file reference is used, if not set in TypoScript.
4029
     *
4030
     * Example TypoScript settings:
4031
     * file.crop =
4032
     * OR
4033
     * file.crop = 50,50,100,100
4034
     * OR
4035
     * file.crop.data = file:current:crop
4036
     *
4037
     * @param FileReference $fileReference
4038
     * @param array $fileArray TypoScript properties for the imgResource type
4039
     * @return Area|null
4040
     */
4041
    protected function getCropAreaFromFileReference(FileReference $fileReference, array $fileArray)
4042
    {
4043
        // Use cropping area from file reference if nothing is configured in TypoScript.
4044
        if (!isset($fileArray['crop']) && !isset($fileArray['crop.'])) {
4045
            // Set crop variant from TypoScript settings. If not set, use default.
4046
            $cropVariant = $fileArray['cropVariant'] ?? 'default';
4047
            $fileCropArea = $this->createCropAreaFromJsonString((string)$fileReference->getProperty('crop'), $cropVariant);
4048
            return $fileCropArea->isEmpty() ? null : $fileCropArea->makeAbsoluteBasedOnFile($fileReference);
4049
        }
4050
4051
        return $this->getCropAreaFromFromTypoScriptSettings($fileReference, $fileArray);
4052
    }
4053
4054
    /**
4055
     * Returns an ImageManipulation\Area object for the given cropVariant (or 'default')
4056
     * or null if the crop settings or crop area is empty.
4057
     *
4058
     * @param FileInterface $file
4059
     * @param array $fileArray
4060
     * @return Area|null
4061
     */
4062
    protected function getCropAreaFromFromTypoScriptSettings(FileInterface $file, array $fileArray)
4063
    {
4064
        /** @var Area $cropArea */
4065
        $cropArea = null;
4066
        // Resolve TypoScript configured cropping.
4067
        $cropSettings = isset($fileArray['crop.'])
4068
            ? $this->stdWrap($fileArray['crop'], $fileArray['crop.'])
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
4069
            : ($fileArray['crop'] ?? null);
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
4070
4071
        if (is_string($cropSettings)) {
4072
            // Set crop variant from TypoScript settings. If not set, use default.
4073
            $cropVariant = $fileArray['cropVariant'] ?? 'default';
4074
            // Get cropArea from CropVariantCollection, if cropSettings is a valid json.
4075
            // CropVariantCollection::create does json_decode.
4076
            $jsonCropArea = $this->createCropAreaFromJsonString($cropSettings, $cropVariant);
4077
            $cropArea = $jsonCropArea->isEmpty() ? null : $jsonCropArea->makeAbsoluteBasedOnFile($file);
4078
4079
            // Cropping is configured in TypoScript in the following way: file.crop = 50,50,100,100
4080
            if ($jsonCropArea->isEmpty() && preg_match('/^[0-9]+,[0-9]+,[0-9]+,[0-9]+$/', $cropSettings)) {
4081
                $cropSettings = explode(',', $cropSettings);
4082
                if (count($cropSettings) === 4) {
4083
                    $stringCropArea = GeneralUtility::makeInstance(
4084
                        Area::class,
4085
                        ...$cropSettings
4086
                    );
4087
                    $cropArea = $stringCropArea->isEmpty() ? null : $stringCropArea;
4088
                }
4089
            }
4090
        }
4091
4092
        return $cropArea;
4093
    }
4094
4095
    /**
4096
     * Takes a JSON string and creates CropVariantCollection and fetches the corresponding
4097
     * CropArea for that.
4098
     *
4099
     * @param string $cropSettings
4100
     * @param string $cropVariant
4101
     * @return Area
4102
     */
4103
    protected function createCropAreaFromJsonString(string $cropSettings, string $cropVariant): Area
4104
    {
4105
        return CropVariantCollection::create($cropSettings)->getCropArea($cropVariant);
4106
    }
4107
4108
    /***********************************************
4109
     *
4110
     * Data retrieval etc.
4111
     *
4112
     ***********************************************/
4113
    /**
4114
     * 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.
4115
     *
4116
     * @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)
4117
     * @return string|null
4118
     */
4119
    public function getFieldVal($field)
4120
    {
4121
        if (strpos($field, '//') === false) {
4122
            return $this->data[trim($field)] ?? null;
4123
        }
4124
        $sections = GeneralUtility::trimExplode('//', $field, true);
4125
        foreach ($sections as $k) {
4126
            if ((string)$this->data[$k] !== '') {
4127
                return $this->data[$k];
4128
            }
4129
        }
4130
4131
        return '';
4132
    }
4133
4134
    /**
4135
     * 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.
4136
     *
4137
     * @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)
4138
     * @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.
4139
     * @return string The value fetched
4140
     * @see getFieldVal()
4141
     */
4142
    public function getData($string, $fieldArray = null)
4143
    {
4144
        $tsfe = $this->getTypoScriptFrontendController();
4145
        if (!is_array($fieldArray)) {
4146
            $fieldArray = $tsfe->page;
4147
        }
4148
        $retVal = '';
4149
        $sections = explode('//', $string);
4150
        foreach ($sections as $secKey => $secVal) {
4151
            if ($retVal) {
4152
                break;
4153
            }
4154
            $parts = explode(':', $secVal, 2);
4155
            $type = strtolower(trim($parts[0]));
4156
            $typesWithOutParameters = ['level', 'date', 'current', 'pagelayout'];
4157
            $key = trim($parts[1] ?? '');
4158
            if (($key != '') || in_array($type, $typesWithOutParameters)) {
4159
                switch ($type) {
4160
                    case 'gp':
4161
                        // Merge GET and POST and get $key out of the merged array
4162
                        $getPostArray = GeneralUtility::_GET();
4163
                        ArrayUtility::mergeRecursiveWithOverrule($getPostArray, GeneralUtility::_POST());
4164
                        $retVal = $this->getGlobal($key, $getPostArray);
4165
                        break;
4166
                    case 'tsfe':
4167
                        $retVal = $this->getGlobal('TSFE|' . $key);
4168
                        break;
4169
                    case 'getenv':
4170
                        $retVal = getenv($key);
4171
                        break;
4172
                    case 'getindpenv':
4173
                        $retVal = $this->getEnvironmentVariable($key);
4174
                        break;
4175
                    case 'field':
4176
                        $retVal = $this->getGlobal($key, $fieldArray);
4177
                        break;
4178
                    case 'file':
4179
                        $retVal = $this->getFileDataKey($key);
4180
                        break;
4181
                    case 'parameters':
4182
                        $retVal = $this->parameters[$key];
4183
                        break;
4184
                    case 'register':
4185
                        $retVal = $tsfe->register[$key] ?? null;
4186
                        break;
4187
                    case 'global':
4188
                        $retVal = $this->getGlobal($key);
4189
                        break;
4190
                    case 'level':
4191
                        $retVal = count($tsfe->tmpl->rootLine) - 1;
4192
                        break;
4193
                    case 'leveltitle':
4194
                        $keyParts = GeneralUtility::trimExplode(',', $key);
4195
                        $numericKey = $this->getKey($keyParts[0], $tsfe->tmpl->rootLine);
0 ignored issues
show
Bug introduced by
$keyParts[0] 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

4195
                        $numericKey = $this->getKey(/** @scrutinizer ignore-type */ $keyParts[0], $tsfe->tmpl->rootLine);
Loading history...
4196
                        $retVal = $this->rootLineValue($numericKey, 'title', strtolower($keyParts[1] ?? '') === 'slide');
4197
                        break;
4198
                    case 'levelmedia':
4199
                        $keyParts = GeneralUtility::trimExplode(',', $key);
4200
                        $numericKey = $this->getKey($keyParts[0], $tsfe->tmpl->rootLine);
4201
                        $retVal = $this->rootLineValue($numericKey, 'media', strtolower($keyParts[1] ?? '') === 'slide');
4202
                        break;
4203
                    case 'leveluid':
4204
                        $numericKey = $this->getKey($key, $tsfe->tmpl->rootLine);
4205
                        $retVal = $this->rootLineValue($numericKey, 'uid');
4206
                        break;
4207
                    case 'levelfield':
4208
                        $keyParts = GeneralUtility::trimExplode(',', $key);
4209
                        $numericKey = $this->getKey($keyParts[0], $tsfe->tmpl->rootLine);
4210
                        $retVal = $this->rootLineValue($numericKey, $keyParts[1], strtolower($keyParts[2] ?? '') === 'slide');
4211
                        break;
4212
                    case 'fullrootline':
4213
                        $keyParts = GeneralUtility::trimExplode(',', $key);
4214
                        $fullKey = (int)$keyParts[0] - count($tsfe->tmpl->rootLine) + count($tsfe->rootLine);
4215
                        if ($fullKey >= 0) {
4216
                            $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

4216
                            $retVal = $this->rootLineValue($fullKey, $keyParts[1], /** @scrutinizer ignore-type */ stristr($keyParts[2] ?? '', 'slide'), $tsfe->rootLine);
Loading history...
4217
                        }
4218
                        break;
4219
                    case 'date':
4220
                        if (!$key) {
4221
                            $key = 'd/m Y';
4222
                        }
4223
                        $retVal = date($key, $GLOBALS['EXEC_TIME']);
4224
                        break;
4225
                    case 'page':
4226
                        $retVal = $tsfe->page[$key];
4227
                        break;
4228
                    case 'pagelayout':
4229
                        $retVal = GeneralUtility::makeInstance(PageLayoutResolver::class)
4230
                            ->getLayoutForPage($tsfe->page, $tsfe->rootLine);
4231
                        break;
4232
                    case 'current':
4233
                        $retVal = $this->data[$this->currentValKey] ?? null;
4234
                        break;
4235
                    case 'db':
4236
                        $selectParts = GeneralUtility::trimExplode(':', $key);
4237
                        $db_rec = $tsfe->sys_page->getRawRecord($selectParts[0], $selectParts[1]);
0 ignored issues
show
Bug introduced by
$selectParts[1] of type string is incompatible with the type integer expected by parameter $uid of TYPO3\CMS\Core\Domain\Re...ository::getRawRecord(). ( Ignorable by Annotation )

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

4237
                        $db_rec = $tsfe->sys_page->getRawRecord($selectParts[0], /** @scrutinizer ignore-type */ $selectParts[1]);
Loading history...
4238
                        if (is_array($db_rec) && $selectParts[2]) {
4239
                            $retVal = $db_rec[$selectParts[2]];
4240
                        }
4241
                        break;
4242
                    case 'lll':
4243
                        $retVal = $tsfe->sL('LLL:' . $key);
4244
                        break;
4245
                    case 'path':
4246
                        try {
4247
                            $retVal = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($key);
4248
                        } catch (Exception $e) {
4249
                            // do nothing in case the file path is invalid
4250
                            $retVal = null;
4251
                        }
4252
                        break;
4253
                    case 'cobj':
4254
                        switch ($key) {
4255
                            case 'parentRecordNumber':
4256
                                $retVal = $this->parentRecordNumber;
4257
                                break;
4258
                        }
4259
                        break;
4260
                    case 'debug':
4261
                        switch ($key) {
4262
                            case 'rootLine':
4263
                                $retVal = DebugUtility::viewArray($tsfe->tmpl->rootLine);
4264
                                break;
4265
                            case 'fullRootLine':
4266
                                $retVal = DebugUtility::viewArray($tsfe->rootLine);
4267
                                break;
4268
                            case 'data':
4269
                                $retVal = DebugUtility::viewArray($this->data);
4270
                                break;
4271
                            case 'register':
4272
                                $retVal = DebugUtility::viewArray($tsfe->register);
4273
                                break;
4274
                            case 'page':
4275
                                $retVal = DebugUtility::viewArray($tsfe->page);
4276
                                break;
4277
                        }
4278
                        break;
4279
                    case 'flexform':
4280
                        $keyParts = GeneralUtility::trimExplode(':', $key, true);
4281
                        if (count($keyParts) === 2 && isset($this->data[$keyParts[0]])) {
4282
                            $flexFormContent = $this->data[$keyParts[0]];
4283
                            if (!empty($flexFormContent)) {
4284
                                $flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
4285
                                $flexFormKey = str_replace('.', '|', $keyParts[1]);
4286
                                $settings = $flexFormService->convertFlexFormContentToArray($flexFormContent);
4287
                                $retVal = $this->getGlobal($flexFormKey, $settings);
4288
                            }
4289
                        }
4290
                        break;
4291
                    case 'session':
4292
                        $keyParts = GeneralUtility::trimExplode('|', $key, true);
4293
                        $sessionKey = array_shift($keyParts);
4294
                        $retVal = $this->getTypoScriptFrontendController()->fe_user->getSessionData($sessionKey);
4295
                        foreach ($keyParts as $keyPart) {
4296
                            if (is_object($retVal)) {
4297
                                $retVal = $retVal->{$keyPart};
4298
                            } elseif (is_array($retVal)) {
4299
                                $retVal = $retVal[$keyPart];
4300
                            } else {
4301
                                $retVal = '';
4302
                                break;
4303
                            }
4304
                        }
4305
                        if (!is_scalar($retVal)) {
4306
                            $retVal = '';
4307
                        }
4308
                        break;
4309
                    case 'context':
4310
                        $context = GeneralUtility::makeInstance(Context::class);
4311
                        [$aspectName, $propertyName] = GeneralUtility::trimExplode(':', $key, true, 2);
4312
                        $retVal = $context->getPropertyFromAspect($aspectName, $propertyName, '');
4313
                        if (is_array($retVal)) {
4314
                            $retVal = implode(',', $retVal);
4315
                        }
4316
                        if (!is_scalar($retVal)) {
4317
                            $retVal = '';
4318
                        }
4319
                        break;
4320
                    case 'site':
4321
                        $site = $this->getTypoScriptFrontendController()->getSite();
4322
                        if ($key === 'identifier') {
4323
                            $retVal = $site->getIdentifier();
4324
                        } elseif ($key === 'base') {
4325
                            $retVal = $site->getBase();
4326
                        } else {
4327
                            try {
4328
                                $retVal = ArrayUtility::getValueByPath($site->getConfiguration(), $key, '.');
4329
                            } catch (MissingArrayPathException $exception) {
4330
                                $this->logger->warning(sprintf('getData() with "%s" failed', $key), ['exception' => $exception]);
4331
                            }
4332
                        }
4333
                        break;
4334
                    case 'sitelanguage':
4335
                        $siteLanguage = $this->getTypoScriptFrontendController()->getLanguage();
4336
                        $config = $siteLanguage->toArray();
4337
                        if (isset($config[$key])) {
4338
                            $retVal = $config[$key];
4339
                        }
4340
                        break;
4341
                }
4342
            }
4343
4344
            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getData'] ?? [] as $className) {
4345
                $hookObject = GeneralUtility::makeInstance($className);
4346
                if (!$hookObject instanceof ContentObjectGetDataHookInterface) {
4347
                    throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetDataHookInterface::class, 1195044480);
4348
                }
4349
                $ref = $this; // introduced for phpstan to not lose type information when passing $this into callUserFunction
4350
                $retVal = $hookObject->getDataExtension($string, $fieldArray, $secVal, $retVal, $ref);
4351
            }
4352
        }
4353
        return $retVal;
4354
    }
4355
4356
    /**
4357
     * Gets file information. This is a helper function for the getData() method above, which resolves e.g.
4358
     * page.10.data = file:current:title
4359
     * or
4360
     * page.10.data = file:17:title
4361
     *
4362
     * @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.)
4363
     * @return string|int The value as retrieved from the file object.
4364
     */
4365
    protected function getFileDataKey($key)
4366
    {
4367
        [$fileUidOrCurrentKeyword, $requestedFileInformationKey] = explode(':', $key, 3);
4368
        try {
4369
            if ($fileUidOrCurrentKeyword === 'current') {
4370
                $fileObject = $this->getCurrentFile();
4371
            } elseif (MathUtility::canBeInterpretedAsInteger($fileUidOrCurrentKeyword)) {
4372
                /** @var ResourceFactory $fileFactory */
4373
                $fileFactory = GeneralUtility::makeInstance(ResourceFactory::class);
4374
                $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

4374
                $fileObject = $fileFactory->getFileObject(/** @scrutinizer ignore-type */ $fileUidOrCurrentKeyword);
Loading history...
4375
            } else {
4376
                $fileObject = null;
4377
            }
4378
        } catch (Exception $exception) {
4379
            $this->logger->warning('The file "' . $fileUidOrCurrentKeyword . '" could not be found and won\'t be included in frontend output', ['exception' => $exception]);
4380
            $fileObject = null;
4381
        }
4382
4383
        if ($fileObject instanceof FileInterface) {
4384
            // All properties of the \TYPO3\CMS\Core\Resource\FileInterface are available here:
4385
            switch ($requestedFileInformationKey) {
4386
                case 'name':
4387
                    return $fileObject->getName();
4388
                case 'uid':
4389
                    if (method_exists($fileObject, 'getUid')) {
4390
                        return $fileObject->getUid();
4391
                    }
4392
                    return 0;
4393
                case 'originalUid':
4394
                    if ($fileObject instanceof FileReference) {
0 ignored issues
show
introduced by
$fileObject is never a sub-type of TYPO3\CMS\Core\Resource\FileReference.
Loading history...
4395
                        return $fileObject->getOriginalFile()->getUid();
4396
                    }
4397
                    return null;
4398
                case 'size':
4399
                    return $fileObject->getSize();
4400
                case 'sha1':
4401
                    return $fileObject->getSha1();
4402
                case 'extension':
4403
                    return $fileObject->getExtension();
4404
                case 'mimetype':
4405
                    return $fileObject->getMimeType();
4406
                case 'contents':
4407
                    return $fileObject->getContents();
4408
                case 'publicUrl':
4409
                    return $fileObject->getPublicUrl();
4410
                default:
4411
                    // Generic alternative here
4412
                    return $fileObject->getProperty($requestedFileInformationKey);
4413
            }
4414
        } else {
4415
            // @todo fail silently as is common in tslib_content
4416
            return 'Error: no file object';
4417
        }
4418
    }
4419
4420
    /**
4421
     * Returns a value from the current rootline (site) from $GLOBALS['TSFE']->tmpl->rootLine;
4422
     *
4423
     * @param string $key Which level in the root line
4424
     * @param string $field The field in the rootline record to return (a field from the pages table)
4425
     * @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
4426
     * @param mixed $altRootLine If you supply an array for this it will be used as an alternative root line array
4427
     * @return string The value from the field of the rootline.
4428
     * @internal
4429
     * @see getData()
4430
     */
4431
    public function rootLineValue($key, $field, $slideBack = false, $altRootLine = '')
4432
    {
4433
        $rootLine = is_array($altRootLine) ? $altRootLine : $this->getTypoScriptFrontendController()->tmpl->rootLine;
4434
        if (!$slideBack) {
4435
            return $rootLine[$key][$field];
4436
        }
4437
        for ($a = $key; $a >= 0; $a--) {
4438
            $val = $rootLine[$a][$field];
4439
            if ($val) {
4440
                return $val;
4441
            }
4442
        }
4443
4444
        return '';
4445
    }
4446
4447
    /**
4448
     * Return global variable where the input string $var defines array keys separated by "|"
4449
     * Example: $var = "HTTP_SERVER_VARS | something" will return the value $GLOBALS['HTTP_SERVER_VARS']['something'] value
4450
     *
4451
     * @param string $keyString Global var key, eg. "HTTP_GET_VAR" or "HTTP_GET_VARS|id" to get the GET parameter "id" back.
4452
     * @param array $source Alternative array than $GLOBAL to get variables from.
4453
     * @return mixed Whatever value. If none, then blank string.
4454
     * @see getData()
4455
     */
4456
    public function getGlobal($keyString, $source = null)
4457
    {
4458
        $keys = explode('|', $keyString);
4459
        $numberOfLevels = count($keys);
4460
        $rootKey = trim($keys[0]);
4461
        $value = isset($source) ? $source[$rootKey] : $GLOBALS[$rootKey];
4462
        for ($i = 1; $i < $numberOfLevels && isset($value); $i++) {
4463
            $currentKey = trim($keys[$i]);
4464
            if (is_object($value)) {
4465
                $value = $value->{$currentKey};
4466
            } elseif (is_array($value)) {
4467
                $value = $value[$currentKey];
4468
            } else {
4469
                $value = '';
4470
                break;
4471
            }
4472
        }
4473
        if (!is_scalar($value)) {
4474
            $value = '';
4475
        }
4476
        return $value;
4477
    }
4478
4479
    /**
4480
     * 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).
4481
     * Example: entrylevel = -1 means that entryLevel ends up pointing at the outermost-level, -2 means the level before the outermost...
4482
     *
4483
     * @param int $key The integer to transform
4484
     * @param array $arr array in which the key should be found.
4485
     * @return int The processed integer key value.
4486
     * @internal
4487
     * @see getData()
4488
     */
4489
    public function getKey($key, $arr)
4490
    {
4491
        $key = (int)$key;
4492
        if (is_array($arr)) {
0 ignored issues
show
introduced by
The condition is_array($arr) is always true.
Loading history...
4493
            if ($key < 0) {
4494
                $key = count($arr) + $key;
4495
            }
4496
            if ($key < 0) {
4497
                $key = 0;
4498
            }
4499
        }
4500
        return $key;
4501
    }
4502
4503
    /***********************************************
4504
     *
4505
     * Link functions (typolink)
4506
     *
4507
     ***********************************************/
4508
    /**
4509
     * called from the typoLink() function
4510
     *
4511
     * does the magic to split the full "typolink" string like "15,13 _blank myclass &more=1"
4512
     * into separate parts
4513
     *
4514
     * @param string $linkText The string (text) to link
4515
     * @param string $mixedLinkParameter destination data like "15,13 _blank myclass &more=1" used to create the link
4516
     * @param array $configuration TypoScript configuration
4517
     * @return array|string
4518
     * @see typoLink()
4519
     *
4520
     * @todo the functionality of the "file:" syntax + the hook should be marked as deprecated, an upgrade wizard should handle existing links
4521
     */
4522
    protected function resolveMixedLinkParameter($linkText, $mixedLinkParameter, &$configuration = [])
4523
    {
4524
        $linkParameter = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $linkParameter is dead and can be removed.
Loading history...
4525
4526
        // Link parameter value = first part
4527
        $linkParameterParts = GeneralUtility::makeInstance(TypoLinkCodecService::class)->decode($mixedLinkParameter);
4528
4529
        // Check for link-handler keyword
4530
        $linkHandlerExploded = explode(':', $linkParameterParts['url'], 2);
4531
        $linkHandlerKeyword = $linkHandlerExploded[0] ?? null;
4532
4533
        if (in_array(strtolower(preg_replace('#\s|[[:cntrl:]]#', '', $linkHandlerKeyword)), ['javascript', 'data'], true)) {
4534
            // Disallow insecure scheme's like javascript: or data:
4535
            return $linkText;
4536
        }
4537
        $linkParameter = $linkParameterParts['url'];
4538
4539
        // additional parameters that need to be set
4540
        if ($linkParameterParts['additionalParams'] !== '') {
4541
            $forceParams = $linkParameterParts['additionalParams'];
4542
            // params value
4543
            $configuration['additionalParams'] .= $forceParams[0] === '&' ? $forceParams : '&' . $forceParams;
4544
        }
4545
4546
        return [
4547
            'href'   => $linkParameter,
4548
            'target' => $linkParameterParts['target'],
4549
            'class'  => $linkParameterParts['class'],
4550
            'title'  => $linkParameterParts['title']
4551
        ];
4552
    }
4553
4554
    /**
4555
     * Implements the "typolink" property of stdWrap (and others)
4556
     * 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.
4557
     * 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.
4558
     * 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.
4559
     * For many more details on the parameters and how they are interpreted, please see the link to TSref below.
4560
     *
4561
     * the FAL API is handled with the namespace/prefix "file:..."
4562
     *
4563
     * @param string $linkText The string (text) to link
4564
     * @param array $conf TypoScript configuration (see link below)
4565
     * @return string A link-wrapped string.
4566
     * @see stdWrap()
4567
     * @see \TYPO3\CMS\Frontend\Plugin\AbstractPlugin::pi_linkTP()
4568
     */
4569
    public function typoLink($linkText, $conf)
4570
    {
4571
        $linkText = (string)$linkText;
4572
        $tsfe = $this->getTypoScriptFrontendController();
4573
4574
        $linkParameter = trim($this->stdWrapValue('parameter', $conf));
4575
        $this->lastTypoLinkUrl = '';
4576
        $this->lastTypoLinkTarget = '';
4577
4578
        $resolvedLinkParameters = $this->resolveMixedLinkParameter($linkText, $linkParameter, $conf);
4579
        // check if the link handler hook has resolved the link completely already
4580
        if (!is_array($resolvedLinkParameters)) {
4581
            return $resolvedLinkParameters;
4582
        }
4583
        $linkParameter = $resolvedLinkParameters['href'];
4584
        $target = $resolvedLinkParameters['target'];
4585
        $title = $resolvedLinkParameters['title'];
4586
4587
        if (!$linkParameter) {
4588
            return $this->resolveAnchorLink($linkText, $conf ?? []);
4589
        }
4590
4591
        // Detecting kind of link and resolve all necessary parameters
4592
        $linkService = GeneralUtility::makeInstance(LinkService::class);
4593
        try {
4594
            $linkDetails = $linkService->resolve($linkParameter);
4595
        } catch (UnknownLinkHandlerException | InvalidPathException $exception) {
4596
            $this->logger->warning('The link could not be generated', ['exception' => $exception]);
4597
            return $linkText;
4598
        }
4599
4600
        $linkDetails['typoLinkParameter'] = $linkParameter;
4601
        if (isset($linkDetails['type']) && isset($GLOBALS['TYPO3_CONF_VARS']['FE']['typolinkBuilder'][$linkDetails['type']])) {
4602
            /** @var AbstractTypolinkBuilder $linkBuilder */
4603
            $linkBuilder = GeneralUtility::makeInstance(
4604
                $GLOBALS['TYPO3_CONF_VARS']['FE']['typolinkBuilder'][$linkDetails['type']],
4605
                $this,
4606
                $tsfe
4607
            );
4608
            try {
4609
                [$this->lastTypoLinkUrl, $linkText, $target] = $linkBuilder->build($linkDetails, $linkText, $target, $conf);
4610
                $this->lastTypoLinkTarget = htmlspecialchars($target);
4611
                $this->lastTypoLinkLD['target'] = htmlspecialchars($target);
4612
                $this->lastTypoLinkLD['totalUrl'] = $this->lastTypoLinkUrl;
4613
            } catch (UnableToLinkException $e) {
4614
                $this->logger->debug(sprintf('Unable to link "%s": %s', $e->getLinkText(), $e->getMessage()), ['exception' => $e]);
4615
4616
                // Only return the link text directly
4617
                return $e->getLinkText();
4618
            }
4619
        } elseif (isset($linkDetails['url'])) {
4620
            $this->lastTypoLinkUrl = $linkDetails['url'];
4621
            $this->lastTypoLinkTarget = htmlspecialchars($target);
4622
            $this->lastTypoLinkLD['target'] = htmlspecialchars($target);
4623
            $this->lastTypoLinkLD['totalUrl'] = $this->lastTypoLinkUrl;
4624
        } else {
4625
            return $linkText;
4626
        }
4627
4628
        // We need to backup the URL because ATagParams might call typolink again and change the last URL.
4629
        $url = $this->lastTypoLinkUrl;
4630
        $finalTagParts = [
4631
            'aTagParams' => $this->getATagParams($conf),
4632
            'url'        => $url,
4633
            'TYPE'       => $linkDetails['type']
4634
        ];
4635
4636
        // Ensure "href" is not in the list of aTagParams to avoid double tags, usually happens within buggy parseFunc settings
4637
        if (!empty($finalTagParts['aTagParams'])) {
4638
            $aTagParams = GeneralUtility::get_tag_attributes($finalTagParts['aTagParams'], true);
4639
            if (isset($aTagParams['href'])) {
4640
                unset($aTagParams['href']);
4641
                $finalTagParts['aTagParams'] = GeneralUtility::implodeAttributes($aTagParams, true);
4642
            }
4643
        }
4644
4645
        // Building the final <a href=".."> tag
4646
        $tagAttributes = [];
4647
4648
        // Title attribute
4649
        if (empty($title)) {
4650
            $title = $conf['title'] ?? '';
4651
            if (isset($conf['title.']) && is_array($conf['title.'])) {
4652
                $title = $this->stdWrap($title, $conf['title.']);
4653
            }
4654
        }
4655
4656
        // Check, if the target is coded as a JS open window link:
4657
        $JSwindowParts = [];
4658
        $JSwindowParams = '';
4659
        if ($target && preg_match('/^([0-9]+)x([0-9]+)(:(.*)|.*)$/', $target, $JSwindowParts)) {
4660
            // Take all pre-configured and inserted parameters and compile parameter list, including width+height:
4661
            $JSwindow_tempParamsArr = GeneralUtility::trimExplode(',', strtolower(($conf['JSwindow_params'] ?? '') . ',' . ($JSwindowParts[4] ?? '')), true);
4662
            $JSwindow_paramsArr = [];
4663
            $target = $conf['target'] ?? 'FEopenLink';
4664
            foreach ($JSwindow_tempParamsArr as $JSv) {
4665
                [$JSp, $JSv] = explode('=', $JSv, 2);
4666
                // If the target is set as JS param, this is extracted
4667
                if ($JSp === 'target') {
4668
                    $target = $JSv;
4669
                } else {
4670
                    $JSwindow_paramsArr[$JSp] = $JSp . '=' . $JSv;
4671
                }
4672
            }
4673
            // Add width/height:
4674
            $JSwindow_paramsArr['width'] = 'width=' . $JSwindowParts[1];
4675
            $JSwindow_paramsArr['height'] = 'height=' . $JSwindowParts[2];
4676
            // Imploding into string:
4677
            $JSwindowParams = implode(',', $JSwindow_paramsArr);
4678
        }
4679
4680
        if (!$JSwindowParams && $linkDetails['type'] === LinkService::TYPE_EMAIL && $tsfe->spamProtectEmailAddresses === 'ascii') {
4681
            $tagAttributes['href'] = $finalTagParts['url'];
4682
        } else {
4683
            $tagAttributes['href'] = htmlspecialchars($finalTagParts['url']);
4684
        }
4685
        if (!empty($title)) {
4686
            $tagAttributes['title'] = htmlspecialchars($title);
4687
        }
4688
4689
        // Target attribute
4690
        if (!empty($target)) {
4691
            $tagAttributes['target'] = htmlspecialchars($target);
4692
        }
4693
        if ($JSwindowParams && in_array($tsfe->xhtmlDoctype, ['xhtml_strict', 'xhtml_11'], true)) {
4694
            // Create TARGET-attribute only if the right doctype is used
4695
            unset($tagAttributes['target']);
4696
        }
4697
4698
        if ($JSwindowParams) {
4699
            $onClick = 'vHWin=window.open(' . GeneralUtility::quoteJSvalue($tsfe->baseUrlWrap($finalTagParts['url']))
4700
                . ',' . GeneralUtility::quoteJSvalue($target) . ','
4701
                . GeneralUtility::quoteJSvalue($JSwindowParams)
4702
                . ');vHWin.focus();return false;';
4703
            $tagAttributes['onclick'] = htmlspecialchars($onClick);
4704
        }
4705
4706
        if (!empty($resolvedLinkParameters['class'])) {
4707
            $tagAttributes['class'] = htmlspecialchars($resolvedLinkParameters['class']);
4708
        }
4709
4710
        // Prevent trouble with double and missing spaces between attributes and merge params before implode
4711
        // (skip decoding HTML entities, since `$tagAttributes` are expected to be encoded already)
4712
        $finalTagAttributes = array_merge($tagAttributes, GeneralUtility::get_tag_attributes($finalTagParts['aTagParams']));
4713
        $finalTagAttributes = $this->addSecurityRelValues($finalTagAttributes, $target, $tagAttributes['href']);
4714
        $finalAnchorTag = '<a ' . GeneralUtility::implodeAttributes($finalTagAttributes) . '>';
4715
4716
        // kept for backwards-compatibility in hooks
4717
        $finalTagParts['targetParams'] = !empty($tagAttributes['target']) ? ' target="' . $tagAttributes['target'] . '"' : '';
4718
        $this->lastTypoLinkTarget = $target;
4719
4720
        // Call user function:
4721
        if ($conf['userFunc'] ?? false) {
4722
            $finalTagParts['TAG'] = $finalAnchorTag;
4723
            $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

4723
            $finalAnchorTag = $this->callUserFunction($conf['userFunc'], $conf['userFunc.'], /** @scrutinizer ignore-type */ $finalTagParts);
Loading history...
4724
        }
4725
4726
        // Hook: Call post processing function for link rendering:
4727
        $_params = [
4728
            'conf' => &$conf,
4729
            'linktxt' => &$linkText,
4730
            'finalTag' => &$finalAnchorTag,
4731
            'finalTagParts' => &$finalTagParts,
4732
            'linkDetails' => &$linkDetails,
4733
            'tagAttributes' => &$finalTagAttributes
4734
        ];
4735
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['typoLink_PostProc'] ?? [] as $_funcRef) {
4736
            $ref = $this; // introduced for phpstan to not lose type information when passing $this into callUserFunction
4737
            GeneralUtility::callUserFunction($_funcRef, $_params, $ref);
4738
        }
4739
4740
        // If flag "returnLastTypoLinkUrl" set, then just return the latest URL made:
4741
        if ($conf['returnLast'] ?? false) {
4742
            switch ($conf['returnLast']) {
4743
                case 'url':
4744
                    return $this->lastTypoLinkUrl;
4745
                case 'target':
4746
                    return $this->lastTypoLinkTarget;
4747
            }
4748
        }
4749
4750
        $wrap = $this->stdWrapValue('wrap', $conf);
4751
4752
        if ($conf['ATagBeforeWrap'] ?? false) {
4753
            return $finalAnchorTag . $this->wrap($linkText, $wrap) . '</a>';
4754
        }
4755
        return $this->wrap($finalAnchorTag . $linkText . '</a>', $wrap);
4756
    }
4757
4758
    protected function addSecurityRelValues(array $tagAttributes, ?string $target, string $url): array
4759
    {
4760
        $relAttribute = 'noreferrer';
4761
        if (in_array($target, ['', null, '_self', '_parent', '_top'], true) || $this->isInternalUrl($url)) {
4762
            return $tagAttributes;
4763
        }
4764
4765
        if (!isset($tagAttributes['rel'])) {
4766
            $tagAttributes['rel'] = $relAttribute;
4767
            return $tagAttributes;
4768
        }
4769
4770
        $tagAttributes['rel'] = implode(' ', array_unique(array_merge(
4771
            GeneralUtility::trimExplode(' ', $relAttribute),
4772
            GeneralUtility::trimExplode(' ', $tagAttributes['rel'])
4773
        )));
4774
4775
        return $tagAttributes;
4776
    }
4777
4778
    /**
4779
     * Checks whether the given url is an internal url.
4780
     *
4781
     * It will check the host part only, against all configured sites
4782
     * whether the given host is any. If so, the url is considered internal
4783
     *
4784
     * @param string $url The url to check.
4785
     * @return bool
4786
     * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
4787
     */
4788
    protected function isInternalUrl(string $url): bool
4789
    {
4790
        $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime');
4791
        $parsedUrl = parse_url($url);
4792
        $foundDomains = 0;
4793
        if (!isset($parsedUrl['host'])) {
4794
            return true;
4795
        }
4796
4797
        $cacheIdentifier = sha1('isInternalDomain' . $parsedUrl['host']);
4798
4799
        if ($cache->has($cacheIdentifier) === false) {
4800
            foreach (GeneralUtility::makeInstance(SiteFinder::class)->getAllSites() as $site) {
4801
                if ($site->getBase()->getHost() === $parsedUrl['host']) {
4802
                    ++$foundDomains;
4803
                    break;
4804
                }
4805
4806
                if ($site->getBase()->getHost() === '' && GeneralUtility::isOnCurrentHost($url)) {
4807
                    ++$foundDomains;
4808
                    break;
4809
                }
4810
            }
4811
4812
            $cache->set($cacheIdentifier, $foundDomains > 0);
4813
        }
4814
4815
        return (bool)$cache->get($cacheIdentifier);
4816
    }
4817
4818
    /**
4819
     * Based on the input "TypoLink" TypoScript configuration this will return the generated URL
4820
     *
4821
     * @param array $conf TypoScript properties for "typolink
4822
     * @return string The URL of the link-tag that typolink() would by itself return
4823
     * @see typoLink()
4824
     */
4825
    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...
4826
    {
4827
        $this->typoLink('|', $conf);
4828
        return $this->lastTypoLinkUrl;
4829
    }
4830
4831
    /**
4832
     * Returns a linked string made from typoLink parameters.
4833
     *
4834
     * 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.
4835
     * Optionally you can supply $urlParameters which is an array with key/value pairs that are rawurlencoded and appended to the resulting url.
4836
     *
4837
     * @param string $label Text string being wrapped by the link.
4838
     * @param string $params Link parameter; eg. "123" for page id, "[email protected]" for email address, "http://...." for URL, "fileadmin/example.txt" for file.
4839
     * @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.
4840
     * @param string $target Specific target set, if any. (Default is using the current)
4841
     * @return string The wrapped $label-text string
4842
     * @see getTypoLink_URL()
4843
     */
4844
    public function getTypoLink($label, $params, $urlParameters = [], $target = '')
4845
    {
4846
        $conf = [];
4847
        $conf['parameter'] = $params;
4848
        if ($target) {
4849
            $conf['target'] = $target;
4850
            $conf['extTarget'] = $target;
4851
            $conf['fileTarget'] = $target;
4852
        }
4853
        if (is_array($urlParameters)) {
4854
            if (!empty($urlParameters)) {
4855
                $conf['additionalParams'] .= HttpUtility::buildQueryString($urlParameters, '&');
4856
            }
4857
        } else {
4858
            $conf['additionalParams'] .= $urlParameters;
4859
        }
4860
        $out = $this->typoLink($label, $conf);
4861
        return $out;
4862
    }
4863
4864
    /**
4865
     * Returns the canonical URL to the current "location", which include the current page ID and type
4866
     * and optionally the query string
4867
     *
4868
     * @param bool $addQueryString Whether additional GET arguments in the query string should be included or not
4869
     * @return string
4870
     */
4871
    public function getUrlToCurrentLocation($addQueryString = true)
4872
    {
4873
        $conf = [];
4874
        $conf['parameter'] = $this->getTypoScriptFrontendController()->id . ',' . $this->getTypoScriptFrontendController()->type;
4875
        if ($addQueryString) {
4876
            $conf['addQueryString'] = '1';
4877
            $linkVars = implode(',', array_keys(GeneralUtility::explodeUrl2Array($this->getTypoScriptFrontendController()->linkVars)));
4878
            $conf['addQueryString.'] = [
4879
                'method' => 'GET',
4880
                'exclude' => 'id,type,cHash' . ($linkVars ? ',' . $linkVars : '')
4881
            ];
4882
        }
4883
4884
        return $this->typoLink_URL($conf);
4885
    }
4886
4887
    /**
4888
     * Returns the URL of a "typolink" create from the input parameter string, url-parameters and target
4889
     *
4890
     * @param string $params Link parameter; eg. "123" for page id, "[email protected]" for email address, "http://...." for URL, "fileadmin/example.txt" for file.
4891
     * @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.
4892
     * @param string $target Specific target set, if any. (Default is using the current)
4893
     * @return string The URL
4894
     * @see getTypoLink()
4895
     */
4896
    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...
4897
    {
4898
        $this->getTypoLink('', $params, $urlParameters, $target);
4899
        return $this->lastTypoLinkUrl;
4900
    }
4901
4902
    /**
4903
     * Loops over all configured URL modifier hooks (if available) and returns the generated URL or NULL if no URL was generated.
4904
     *
4905
     * @param string $context The context in which the method is called (e.g. typoLink).
4906
     * @param string $url The URL that should be processed.
4907
     * @param array $typolinkConfiguration The current link configuration array.
4908
     * @return string|null Returns NULL if URL was not processed or the processed URL as a string.
4909
     * @throws \RuntimeException if a hook was registered but did not fulfill the correct parameters.
4910
     */
4911
    protected function processUrl($context, $url, $typolinkConfiguration = [])
4912
    {
4913
        $urlProcessors = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['urlProcessing']['urlProcessors'] ?? [];
4914
        if (empty($urlProcessors)) {
4915
            return $url;
4916
        }
4917
4918
        foreach ($urlProcessors as $identifier => $configuration) {
4919
            if (empty($configuration) || !is_array($configuration)) {
4920
                throw new \RuntimeException('Missing configuration for URI processor "' . $identifier . '".', 1442050529);
4921
            }
4922
            if (!is_string($configuration['processor']) || empty($configuration['processor']) || !class_exists($configuration['processor']) || !is_subclass_of($configuration['processor'], UrlProcessorInterface::class)) {
4923
                throw new \RuntimeException('The URI processor "' . $identifier . '" defines an invalid provider. Ensure the class exists and implements the "' . UrlProcessorInterface::class . '".', 1442050579);
4924
            }
4925
        }
4926
4927
        $orderedProcessors = GeneralUtility::makeInstance(DependencyOrderingService::class)->orderByDependencies($urlProcessors);
4928
        $keepProcessing = true;
4929
4930
        foreach ($orderedProcessors as $configuration) {
4931
            /** @var UrlProcessorInterface $urlProcessor */
4932
            $urlProcessor = GeneralUtility::makeInstance($configuration['processor']);
4933
            $url = $urlProcessor->process($context, $url, $typolinkConfiguration, $this, $keepProcessing);
4934
            if (!$keepProcessing) {
4935
                break;
4936
            }
4937
        }
4938
4939
        return $url;
4940
    }
4941
4942
    /**
4943
     * Creates a href attibute for given $mailAddress.
4944
     * The function uses spamProtectEmailAddresses for encoding the mailto statement.
4945
     * If spamProtectEmailAddresses is disabled, it'll just return a string like "mailto:[email protected]".
4946
     *
4947
     * @param string $mailAddress Email address
4948
     * @param string $linktxt Link text, default will be the email address.
4949
     * @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.
4950
     */
4951
    public function getMailTo($mailAddress, $linktxt)
4952
    {
4953
        $mailAddress = (string)$mailAddress;
4954
        if ((string)$linktxt === '') {
4955
            $linktxt = htmlspecialchars($mailAddress);
4956
        }
4957
4958
        $originalMailToUrl = 'mailto:' . $mailAddress;
4959
        $mailToUrl = $this->processUrl(UrlProcessorInterface::CONTEXT_MAIL, $originalMailToUrl);
4960
4961
        // no processing happened, therefore, the default processing kicks in
4962
        if ($mailToUrl === $originalMailToUrl) {
4963
            $tsfe = $this->getTypoScriptFrontendController();
4964
            if ($tsfe->spamProtectEmailAddresses) {
4965
                $mailToUrl = $this->encryptEmail($mailToUrl, $tsfe->spamProtectEmailAddresses);
4966
                if ($tsfe->spamProtectEmailAddresses !== 'ascii') {
4967
                    $encodedForJsAndHref = rawurlencode(GeneralUtility::quoteJSvalue($mailToUrl));
4968
                    $mailToUrl = 'javascript:linkTo_UnCryptMailto(' . $encodedForJsAndHref . ');';
4969
                }
4970
                $atLabel = trim($tsfe->config['config']['spamProtectEmailAddresses_atSubst']) ?: '(at)';
4971
                $spamProtectedMailAddress = str_replace('@', $atLabel, htmlspecialchars($mailAddress));
4972
                if ($tsfe->config['config']['spamProtectEmailAddresses_lastDotSubst']) {
4973
                    $lastDotLabel = trim($tsfe->config['config']['spamProtectEmailAddresses_lastDotSubst']);
4974
                    $lastDotLabel = $lastDotLabel ?: '(dot)';
4975
                    $spamProtectedMailAddress = preg_replace('/\\.([^\\.]+)$/', $lastDotLabel . '$1', $spamProtectedMailAddress);
4976
                }
4977
                $linktxt = str_ireplace($mailAddress, $spamProtectedMailAddress, $linktxt);
4978
            }
4979
        }
4980
4981
        return [$mailToUrl, $linktxt];
4982
    }
4983
4984
    /**
4985
     * Encryption of email addresses for <A>-tags See the spam protection setup in TS 'config.'
4986
     *
4987
     * @param string $string Input string to en/decode: "mailto:[email protected]
4988
     * @param mixed  $type - either "ascii" or a number between -10 and 10, taken from config.spamProtectEmailAddresses
4989
     * @return string encoded version of $string
4990
     */
4991
    protected function encryptEmail(string $string, $type): string
4992
    {
4993
        $out = '';
4994
        // obfuscates using the decimal HTML entity references for each character
4995
        if ($type === 'ascii') {
4996
            foreach (preg_split('//u', $string, -1, PREG_SPLIT_NO_EMPTY) as $char) {
4997
                $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

4997
                $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...
4998
            }
4999
        } else {
5000
            // like str_rot13() but with a variable offset and a wider character range
5001
            $len = strlen($string);
5002
            $offset = (int)$type;
5003
            for ($i = 0; $i < $len; $i++) {
5004
                $charValue = ord($string[$i]);
5005
                // 0-9 . , - + / :
5006
                if ($charValue >= 43 && $charValue <= 58) {
5007
                    $out .= $this->encryptCharcode($charValue, 43, 58, $offset);
5008
                } elseif ($charValue >= 64 && $charValue <= 90) {
5009
                    // A-Z @
5010
                    $out .= $this->encryptCharcode($charValue, 64, 90, $offset);
5011
                } elseif ($charValue >= 97 && $charValue <= 122) {
5012
                    // a-z
5013
                    $out .= $this->encryptCharcode($charValue, 97, 122, $offset);
5014
                } else {
5015
                    $out .= $string[$i];
5016
                }
5017
            }
5018
        }
5019
        return $out;
5020
    }
5021
5022
    /**
5023
     * Encryption (or decryption) of a single character.
5024
     * Within the given range the character is shifted with the supplied offset.
5025
     *
5026
     * @param int $n Ordinal of input character
5027
     * @param int $start Start of range
5028
     * @param int $end End of range
5029
     * @param int $offset Offset
5030
     * @return string encoded/decoded version of character
5031
     */
5032
    protected function encryptCharcode($n, $start, $end, $offset)
5033
    {
5034
        $n = $n + $offset;
5035
        if ($offset > 0 && $n > $end) {
5036
            $n = $start + ($n - $end - 1);
5037
        } elseif ($offset < 0 && $n < $start) {
5038
            $n = $end - ($start - $n - 1);
5039
        }
5040
        return chr($n);
5041
    }
5042
5043
    /**
5044
     * Gets the query arguments and assembles them for URLs.
5045
     * Arguments may be removed or set, depending on configuration.
5046
     *
5047
     * @param array $conf Configuration
5048
     * @param array $overruleQueryArguments Multidimensional key/value pairs that overrule incoming query arguments
5049
     * @param bool $forceOverruleArguments If set, key/value pairs not in the query but the overrule array will be set
5050
     * @return string The URL query part (starting with a &)
5051
     */
5052
    public function getQueryArguments($conf, $overruleQueryArguments = [], $forceOverruleArguments = false)
5053
    {
5054
        $exclude = [];
5055
        $method = (string)($conf['method'] ?? '');
5056
        if ($method === 'GET') {
5057
            $currentQueryArray = GeneralUtility::_GET();
5058
        } else {
5059
            $currentQueryArray = [];
5060
            parse_str($this->getEnvironmentVariable('QUERY_STRING'), $currentQueryArray);
5061
        }
5062
        if ($conf['exclude'] ?? false) {
5063
            $excludeString = str_replace(',', '&', $conf['exclude']);
5064
            $excludedQueryParts = [];
5065
            parse_str($excludeString, $excludedQueryParts);
5066
            // never repeat id
5067
            $exclude['id'] = 0;
5068
            $newQueryArray = ArrayUtility::arrayDiffAssocRecursive($currentQueryArray, $excludedQueryParts);
5069
        } else {
5070
            $newQueryArray = $currentQueryArray;
5071
        }
5072
        ArrayUtility::mergeRecursiveWithOverrule($newQueryArray, $overruleQueryArguments, $forceOverruleArguments);
5073
        return HttpUtility::buildQueryString($newQueryArray, '&');
5074
    }
5075
5076
    /***********************************************
5077
     *
5078
     * Miscellaneous functions, stand alone
5079
     *
5080
     ***********************************************/
5081
    /**
5082
     * Wrapping a string.
5083
     * Implements the TypoScript "wrap" property.
5084
     * Example: $content = "HELLO WORLD" and $wrap = "<strong> | </strong>", result: "<strong>HELLO WORLD</strong>"
5085
     *
5086
     * @param string $content The content to wrap
5087
     * @param string $wrap The wrap value, eg. "<strong> | </strong>
5088
     * @param string $char The char used to split the wrapping value, default is "|
5089
     * @return string Wrapped input string
5090
     * @see noTrimWrap()
5091
     */
5092
    public function wrap($content, $wrap, $char = '|')
5093
    {
5094
        if ($wrap) {
5095
            $wrapArr = explode($char, $wrap);
5096
            $content = trim($wrapArr[0] ?? '') . $content . trim($wrapArr[1] ?? '');
5097
        }
5098
        return $content;
5099
    }
5100
5101
    /**
5102
     * Wrapping a string, preserving whitespace in wrap value.
5103
     * Notice that the wrap value uses part 1/2 to wrap (and not 0/1 which wrap() does)
5104
     *
5105
     * @param string $content The content to wrap, eg. "HELLO WORLD
5106
     * @param string $wrap The wrap value, eg. " | <strong> | </strong>
5107
     * @param string $char The char used to split the wrapping value, default is "|"
5108
     * @return string Wrapped input string, eg. " <strong> HELLO WORD </strong>
5109
     * @see wrap()
5110
     */
5111
    public function noTrimWrap($content, $wrap, $char = '|')
5112
    {
5113
        if ($wrap) {
5114
            // expects to be wrapped with (at least) 3 characters (before, middle, after)
5115
            // anything else is not taken into account
5116
            $wrapArr = explode($char, $wrap, 4);
5117
            $content = $wrapArr[1] . $content . $wrapArr[2];
5118
        }
5119
        return $content;
5120
    }
5121
5122
    /**
5123
     * Calling a user function/class-method
5124
     * Notice: For classes the instantiated object will have the internal variable, $cObj, set to be a *reference* to $this (the parent/calling object).
5125
     *
5126
     * @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.
5127
     * @param array $conf The TypoScript configuration to pass the function
5128
     * @param string $content The content string to pass the function
5129
     * @return string The return content from the function call. Should probably be a string.
5130
     * @see stdWrap()
5131
     * @see typoLink()
5132
     * @see _parseFunc()
5133
     */
5134
    public function callUserFunction($funcName, $conf, $content)
5135
    {
5136
        // Split parts
5137
        $parts = explode('->', $funcName);
5138
        if (count($parts) === 2) {
5139
            // Check whether PHP class is available
5140
            if (class_exists($parts[0])) {
5141
                if ($this->container && $this->container->has($parts[0])) {
5142
                    $classObj = $this->container->get($parts[0]);
5143
                } else {
5144
                    $classObj = GeneralUtility::makeInstance($parts[0]);
5145
                }
5146
                if (is_object($classObj) && method_exists($classObj, $parts[1])) {
5147
                    $classObj->cObj = $this;
5148
                    $content = call_user_func_array([
5149
                        $classObj,
5150
                        $parts[1]
5151
                    ], [
5152
                        $content,
5153
                        $conf
5154
                    ]);
5155
                } else {
5156
                    $this->getTimeTracker()->setTSlogMessage('Method "' . $parts[1] . '" did not exist in class "' . $parts[0] . '"', 3);
5157
                }
5158
            } else {
5159
                $this->getTimeTracker()->setTSlogMessage('Class "' . $parts[0] . '" did not exist', 3);
5160
            }
5161
        } elseif (function_exists($funcName)) {
5162
            $content = call_user_func($funcName, $content, $conf);
5163
        } else {
5164
            $this->getTimeTracker()->setTSlogMessage('Function "' . $funcName . '" did not exist', 3);
5165
        }
5166
        return $content;
5167
    }
5168
5169
    /**
5170
     * Cleans up a string of keywords. Keywords at splitted by "," (comma)  ";" (semi colon) and linebreak
5171
     *
5172
     * @param string $content String of keywords
5173
     * @return string Cleaned up string, keywords will be separated by a comma only.
5174
     */
5175
    public function keywords($content)
5176
    {
5177
        $listArr = preg_split('/[,;' . LF . ']/', $content);
5178
        foreach ($listArr as $k => $v) {
5179
            $listArr[$k] = trim($v);
5180
        }
5181
        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

5181
        return implode(',', /** @scrutinizer ignore-type */ $listArr);
Loading history...
5182
    }
5183
5184
    /**
5185
     * Changing character case of a string, converting typically used western charset characters as well.
5186
     *
5187
     * @param string $theValue The string to change case for.
5188
     * @param string $case The direction; either "upper" or "lower
5189
     * @return string
5190
     * @see HTMLcaseshift()
5191
     */
5192
    public function caseshift($theValue, $case)
5193
    {
5194
        switch (strtolower($case)) {
5195
            case 'upper':
5196
                $theValue = mb_strtoupper($theValue, 'utf-8');
5197
                break;
5198
            case 'lower':
5199
                $theValue = mb_strtolower($theValue, 'utf-8');
5200
                break;
5201
            case 'capitalize':
5202
                $theValue = mb_convert_case($theValue, MB_CASE_TITLE, 'utf-8');
5203
                break;
5204
            case 'ucfirst':
5205
                $firstChar = mb_substr($theValue, 0, 1, 'utf-8');
5206
                $firstChar = mb_strtoupper($firstChar, 'utf-8');
5207
                $remainder = mb_substr($theValue, 1, null, 'utf-8');
5208
                $theValue = $firstChar . $remainder;
5209
                break;
5210
            case 'lcfirst':
5211
                $firstChar = mb_substr($theValue, 0, 1, 'utf-8');
5212
                $firstChar = mb_strtolower($firstChar, 'utf-8');
5213
                $remainder = mb_substr($theValue, 1, null, 'utf-8');
5214
                $theValue = $firstChar . $remainder;
5215
                break;
5216
            case 'uppercamelcase':
5217
                $theValue = GeneralUtility::underscoredToUpperCamelCase($theValue);
5218
                break;
5219
            case 'lowercamelcase':
5220
                $theValue = GeneralUtility::underscoredToLowerCamelCase($theValue);
5221
                break;
5222
        }
5223
        return $theValue;
5224
    }
5225
5226
    /**
5227
     * Shifts the case of characters outside of HTML tags in the input string
5228
     *
5229
     * @param string $theValue The string to change case for.
5230
     * @param string $case The direction; either "upper" or "lower
5231
     * @return string
5232
     * @see caseshift()
5233
     */
5234
    public function HTMLcaseshift($theValue, $case)
5235
    {
5236
        $inside = 0;
5237
        $newVal = '';
5238
        $pointer = 0;
5239
        $totalLen = strlen($theValue);
5240
        do {
5241
            if (!$inside) {
5242
                $len = strcspn(substr($theValue, $pointer), '<');
5243
                $newVal .= $this->caseshift(substr($theValue, $pointer, $len), $case);
5244
                $inside = 1;
5245
            } else {
5246
                $len = strcspn(substr($theValue, $pointer), '>') + 1;
5247
                $newVal .= substr($theValue, $pointer, $len);
5248
                $inside = 0;
5249
            }
5250
            $pointer += $len;
5251
        } while ($pointer < $totalLen);
5252
        return $newVal;
5253
    }
5254
5255
    /**
5256
     * Returns the 'age' of the tstamp $seconds
5257
     *
5258
     * @param int $seconds Seconds to return age for. Example: "70" => "1 min", "3601" => "1 hrs
5259
     * @param string $labels The labels of the individual units. Defaults to : ' min| hrs| days| yrs'
5260
     * @return string The formatted string
5261
     */
5262
    public function calcAge($seconds, $labels)
5263
    {
5264
        if (MathUtility::canBeInterpretedAsInteger($labels)) {
5265
            $labels = ' min| hrs| days| yrs| min| hour| day| year';
5266
        } else {
5267
            $labels = str_replace('"', '', $labels);
5268
        }
5269
        $labelArr = explode('|', $labels);
5270
        if (count($labelArr) === 4) {
5271
            $labelArr = array_merge($labelArr, $labelArr);
5272
        }
5273
        $absSeconds = abs($seconds);
5274
        $sign = $seconds > 0 ? 1 : -1;
5275
        if ($absSeconds < 3600) {
5276
            $val = round($absSeconds / 60);
5277
            $seconds = $sign * $val . ($val == 1 ? $labelArr[4] : $labelArr[0]);
5278
        } elseif ($absSeconds < 24 * 3600) {
5279
            $val = round($absSeconds / 3600);
5280
            $seconds = $sign * $val . ($val == 1 ? $labelArr[5] : $labelArr[1]);
5281
        } elseif ($absSeconds < 365 * 24 * 3600) {
5282
            $val = round($absSeconds / (24 * 3600));
5283
            $seconds = $sign * $val . ($val == 1 ? $labelArr[6] : $labelArr[2]);
5284
        } else {
5285
            $val = round($absSeconds / (365 * 24 * 3600));
5286
            $seconds = $sign * $val . ($val == 1 ? ($labelArr[7] ?? null) : ($labelArr[3] ?? null));
5287
        }
5288
        return $seconds;
5289
    }
5290
5291
    /**
5292
     * Resolves a TypoScript reference value to the full set of properties BUT overridden with any local properties set.
5293
     * So the reference is resolved but overlaid with local TypoScript properties of the reference value.
5294
     *
5295
     * @param array $confArr The TypoScript array
5296
     * @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.
5297
     * @return array The modified TypoScript array
5298
     */
5299
    public function mergeTSRef($confArr, $prop)
5300
    {
5301
        if ($confArr[$prop][0] === '<') {
5302
            $key = trim(substr($confArr[$prop], 1));
5303
            $cF = GeneralUtility::makeInstance(TypoScriptParser::class);
5304
            // $name and $conf is loaded with the referenced values.
5305
            $old_conf = $confArr[$prop . '.'];
5306
            [, $conf] = $cF->getVal($key, $this->getTypoScriptFrontendController()->tmpl->setup);
5307
            if (is_array($old_conf) && !empty($old_conf)) {
5308
                $conf = is_array($conf) ? array_replace_recursive($conf, $old_conf) : $old_conf;
5309
            }
5310
            $confArr[$prop . '.'] = $conf;
5311
        }
5312
        return $confArr;
5313
    }
5314
5315
    /***********************************************
5316
     *
5317
     * Database functions, making of queries
5318
     *
5319
     ***********************************************/
5320
    /**
5321
     * Generates a list of Page-uid's from $id. List does not include $id itself
5322
     * (unless the id specified is negative in which case it does!)
5323
     * The only pages WHICH PREVENTS DECENDING in a branch are
5324
     * - deleted pages,
5325
     * - pages in a recycler (doktype = 255) or of the Backend User Section (doktpe = 6) type
5326
     * - pages that has the extendToSubpages set, WHERE start/endtime, hidden
5327
     * and fe_users would hide the records.
5328
     * Apart from that, pages with enable-fields excluding them, will also be
5329
     * removed. HOWEVER $dontCheckEnableFields set will allow
5330
     * enableFields-excluded pages to be included anyway - including
5331
     * extendToSubpages sections!
5332
     * Mount Pages are also descended but notice that these ID numbers are not
5333
     * useful for links unless the correct MPvar is set.
5334
     *
5335
     * @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!
5336
     * @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...)
5337
     * @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'
5338
     * @param bool $dontCheckEnableFields See function description
5339
     * @param string $addSelectFields Additional fields to select. Syntax: ",[fieldname],[fieldname],...
5340
     * @param string $moreWhereClauses Additional where clauses. Syntax: " AND [fieldname]=[value] AND ...
5341
     * @param array $prevId_array array of IDs from previous recursions. In order to prevent infinite loops with mount pages.
5342
     * @param int $recursionLevel Internal: Zero for the first recursion, incremented for each recursive call.
5343
     * @return string Returns the list of ids as a comma separated string
5344
     * @see TypoScriptFrontendController::checkEnableFields()
5345
     * @see TypoScriptFrontendController::checkPagerecordForIncludeSection()
5346
     */
5347
    public function getTreeList($id, $depth, $begin = 0, $dontCheckEnableFields = false, $addSelectFields = '', $moreWhereClauses = '', array $prevId_array = [], $recursionLevel = 0)
5348
    {
5349
        $id = (int)$id;
5350
        if (!$id) {
5351
            return '';
5352
        }
5353
5354
        // Init vars:
5355
        $allFields = 'uid,hidden,starttime,endtime,fe_group,extendToSubpages,doktype,php_tree_stop,mount_pid,mount_pid_ol,t3ver_state' . $addSelectFields;
5356
        $depth = (int)$depth;
5357
        $begin = (int)$begin;
5358
        $theList = [];
5359
        $addId = 0;
5360
        $requestHash = '';
5361
5362
        // First level, check id (second level, this is done BEFORE the recursive call)
5363
        $tsfe = $this->getTypoScriptFrontendController();
5364
        if (!$recursionLevel) {
5365
            // Check tree list cache
5366
            // First, create the hash for this request - not sure yet whether we need all these parameters though
5367
            $parameters = [
5368
                $id,
5369
                $depth,
5370
                $begin,
5371
                $dontCheckEnableFields,
5372
                $addSelectFields,
5373
                $moreWhereClauses,
5374
                $prevId_array,
5375
                GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('frontend.user', 'groupIds', [0, -1])
5376
            ];
5377
            $requestHash = md5(serialize($parameters));
5378
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
5379
                ->getQueryBuilderForTable('cache_treelist');
5380
            $cacheEntry = $queryBuilder->select('treelist')
5381
                ->from('cache_treelist')
5382
                ->where(
5383
                    $queryBuilder->expr()->eq(
5384
                        'md5hash',
5385
                        $queryBuilder->createNamedParameter($requestHash, \PDO::PARAM_STR)
5386
                    ),
5387
                    $queryBuilder->expr()->orX(
5388
                        $queryBuilder->expr()->gt(
5389
                            'expires',
5390
                            $queryBuilder->createNamedParameter($GLOBALS['EXEC_TIME'], \PDO::PARAM_INT)
5391
                        ),
5392
                        $queryBuilder->expr()->eq('expires', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
5393
                    )
5394
                )
5395
                ->setMaxResults(1)
5396
                ->execute()
5397
                ->fetch();
5398
5399
            if (is_array($cacheEntry)) {
5400
                // Cache hit
5401
                return $cacheEntry['treelist'];
5402
            }
5403
            // If Id less than zero it means we should add the real id to list:
5404
            if ($id < 0) {
5405
                $addId = $id = abs($id);
5406
            }
5407
            // Check start page:
5408
            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

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

5410
                $mount_info = $tsfe->sys_page->getMountPointInfo(/** @scrutinizer ignore-type */ $id);
Loading history...
5411
                if (is_array($mount_info)) {
5412
                    $id = $mount_info['mount_pid'];
5413
                    // In Overlay mode, use the mounted page uid as added ID!:
5414
                    if ($addId && $mount_info['overlay']) {
5415
                        $addId = $id;
5416
                    }
5417
                }
5418
            } else {
5419
                // Return blank if the start page was NOT found at all!
5420
                return '';
5421
            }
5422
        }
5423
        // Add this ID to the array of IDs
5424
        if ($begin <= 0) {
5425
            $prevId_array[] = $id;
5426
        }
5427
        // Select sublevel:
5428
        if ($depth > 0) {
5429
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
5430
            $queryBuilder->getRestrictions()
5431
                ->removeAll()
5432
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
5433
            $queryBuilder->select(...GeneralUtility::trimExplode(',', $allFields, true))
5434
                ->from('pages')
5435
                ->where(
5436
                    $queryBuilder->expr()->eq(
5437
                        'pid',
5438
                        $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
5439
                    ),
5440
                    // tree is only built by language=0 pages
5441
                    $queryBuilder->expr()->eq('sys_language_uid', 0)
5442
                )
5443
                ->orderBy('sorting');
5444
5445
            if (!empty($moreWhereClauses)) {
5446
                $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($moreWhereClauses));
5447
            }
5448
5449
            $result = $queryBuilder->execute();
5450
            while ($row = $result->fetch()) {
5451
                /** @var VersionState $versionState */
5452
                $versionState = VersionState::cast($row['t3ver_state']);
5453
                $tsfe->sys_page->versionOL('pages', $row);
5454
                if ((int)$row['doktype'] === PageRepository::DOKTYPE_RECYCLER
5455
                    || (int)$row['doktype'] === PageRepository::DOKTYPE_BE_USER_SECTION
5456
                    || $versionState->indicatesPlaceholder()
5457
                ) {
5458
                    // Doing this after the overlay to make sure changes
5459
                    // in the overlay are respected.
5460
                    // However, we do not process pages below of and
5461
                    // including of type recycler and BE user section
5462
                    continue;
5463
                }
5464
                // Find mount point if any:
5465
                $next_id = $row['uid'];
5466
                $mount_info = $tsfe->sys_page->getMountPointInfo($next_id, $row);
5467
                // Overlay mode:
5468
                if (is_array($mount_info) && $mount_info['overlay']) {
5469
                    $next_id = $mount_info['mount_pid'];
5470
                    $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
5471
                        ->getQueryBuilderForTable('pages');
5472
                    $queryBuilder->getRestrictions()
5473
                        ->removeAll()
5474
                        ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
5475
                    $queryBuilder->select(...GeneralUtility::trimExplode(',', $allFields, true))
5476
                        ->from('pages')
5477
                        ->where(
5478
                            $queryBuilder->expr()->eq(
5479
                                'uid',
5480
                                $queryBuilder->createNamedParameter($next_id, \PDO::PARAM_INT)
5481
                            )
5482
                        )
5483
                        ->orderBy('sorting')
5484
                        ->setMaxResults(1);
5485
5486
                    if (!empty($moreWhereClauses)) {
5487
                        $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($moreWhereClauses));
5488
                    }
5489
5490
                    $row = $queryBuilder->execute()->fetch();
5491
                    $tsfe->sys_page->versionOL('pages', $row);
5492
                    if ((int)$row['doktype'] === PageRepository::DOKTYPE_RECYCLER
5493
                        || (int)$row['doktype'] === PageRepository::DOKTYPE_BE_USER_SECTION
5494
                        || $versionState->indicatesPlaceholder()
5495
                    ) {
5496
                        // Doing this after the overlay to make sure
5497
                        // changes in the overlay are respected.
5498
                        // see above
5499
                        continue;
5500
                    }
5501
                }
5502
                // Add record:
5503
                if ($dontCheckEnableFields || $tsfe->checkPagerecordForIncludeSection($row)) {
5504
                    // Add ID to list:
5505
                    if ($begin <= 0) {
5506
                        if ($dontCheckEnableFields || $tsfe->checkEnableFields($row)) {
5507
                            $theList[] = $next_id;
5508
                        }
5509
                    }
5510
                    // Next level:
5511
                    if ($depth > 1 && !$row['php_tree_stop']) {
5512
                        // Normal mode:
5513
                        if (is_array($mount_info) && !$mount_info['overlay']) {
5514
                            $next_id = $mount_info['mount_pid'];
5515
                        }
5516
                        // Call recursively, if the id is not in prevID_array:
5517
                        if (!in_array($next_id, $prevId_array)) {
5518
                            $theList = array_merge(
5519
                                GeneralUtility::intExplode(
5520
                                    ',',
5521
                                    $this->getTreeList(
5522
                                        $next_id,
5523
                                        $depth - 1,
5524
                                        $begin - 1,
5525
                                        $dontCheckEnableFields,
5526
                                        $addSelectFields,
5527
                                        $moreWhereClauses,
5528
                                        $prevId_array,
5529
                                        $recursionLevel + 1
5530
                                    ),
5531
                                    true
5532
                                ),
5533
                                $theList
5534
                            );
5535
                        }
5536
                    }
5537
                }
5538
            }
5539
        }
5540
        // If first run, check if the ID should be returned:
5541
        if (!$recursionLevel) {
5542
            if ($addId) {
5543
                if ($begin > 0) {
5544
                    $theList[] = 0;
5545
                } else {
5546
                    $theList[] = $addId;
5547
                }
5548
            }
5549
5550
            $cacheEntry = [
5551
                'md5hash' => $requestHash,
5552
                'pid' => $id,
5553
                'treelist' => implode(',', $theList),
5554
                'tstamp' => $GLOBALS['EXEC_TIME'],
5555
            ];
5556
5557
            // Only add to cache if not logged into TYPO3 Backend
5558
            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...
5559
                $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('cache_treelist');
5560
                try {
5561
                    $connection->transactional(function ($connection) use ($cacheEntry) {
5562
                        $connection->insert('cache_treelist', $cacheEntry);
5563
                    });
5564
                } catch (\Throwable $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
5565
                }
5566
            }
5567
        }
5568
5569
        return implode(',', $theList);
5570
    }
5571
5572
    /**
5573
     * Generates a search where clause based on the input search words (AND operation - all search words must be found in record.)
5574
     * 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%")'
5575
     *
5576
     * @param string $searchWords The search words. These will be separated by space and comma.
5577
     * @param string $searchFieldList The fields to search in
5578
     * @param string $searchTable The table name you search in (recommended for DBAL compliance. Will be prepended field names as well)
5579
     * @return string The WHERE clause.
5580
     */
5581
    public function searchWhere($searchWords, $searchFieldList, $searchTable)
5582
    {
5583
        if (!$searchWords) {
5584
            return '';
5585
        }
5586
5587
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
5588
            ->getQueryBuilderForTable($searchTable);
5589
5590
        $prefixTableName = $searchTable ? $searchTable . '.' : '';
5591
5592
        $where = $queryBuilder->expr()->andX();
5593
        $searchFields = explode(',', $searchFieldList);
5594
        $searchWords = preg_split('/[ ,]/', $searchWords);
5595
        foreach ($searchWords as $searchWord) {
5596
            $searchWord = trim($searchWord);
5597
            if (strlen($searchWord) < 3) {
5598
                continue;
5599
            }
5600
            $searchWordConstraint = $queryBuilder->expr()->orX();
5601
            $searchWord = $queryBuilder->escapeLikeWildcards($searchWord);
5602
            foreach ($searchFields as $field) {
5603
                $searchWordConstraint->add(
5604
                    $queryBuilder->expr()->like($prefixTableName . $field, $queryBuilder->quote('%' . $searchWord . '%'))
5605
                );
5606
            }
5607
5608
            if ($searchWordConstraint->count()) {
5609
                $where->add($searchWordConstraint);
5610
            }
5611
        }
5612
5613
        if ((string)$where === '') {
5614
            return '';
5615
        }
5616
5617
        return ' AND (' . (string)$where . ')';
5618
    }
5619
5620
    /**
5621
     * Executes a SELECT query for records from $table and with conditions based on the configuration in the $conf array
5622
     * This function is preferred over ->getQuery() if you just need to create and then execute a query.
5623
     *
5624
     * @param string $table The table name
5625
     * @param array $conf The TypoScript configuration properties
5626
     * @return Statement
5627
     * @see getQuery()
5628
     */
5629
    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...
5630
    {
5631
        $statement = $this->getQuery($table, $conf);
5632
        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
5633
5634
        return $connection->executeQuery($statement);
5635
    }
5636
5637
    /**
5638
     * Executes a SELECT query for records from $table and with conditions based on the configuration in the $conf array
5639
     * and overlays with translation and version if available
5640
     *
5641
     * @param string $tableName the name of the TCA database table
5642
     * @param array $queryConfiguration The TypoScript configuration properties, see .select in TypoScript reference
5643
     * @return array The records
5644
     * @throws \UnexpectedValueException
5645
     */
5646
    public function getRecords($tableName, array $queryConfiguration)
5647
    {
5648
        $records = [];
5649
5650
        $statement = $this->exec_getQuery($tableName, $queryConfiguration);
5651
5652
        $tsfe = $this->getTypoScriptFrontendController();
5653
        while ($row = $statement->fetch()) {
5654
            // Versioning preview:
5655
            $tsfe->sys_page->versionOL($tableName, $row, true);
5656
5657
            // Language overlay:
5658
            if (is_array($row)) {
5659
                $row = $tsfe->sys_page->getLanguageOverlay($tableName, $row);
5660
            }
5661
5662
            // Might be unset in the language overlay
5663
            if (is_array($row)) {
5664
                $records[] = $row;
5665
            }
5666
        }
5667
5668
        return $records;
5669
    }
5670
5671
    /**
5672
     * Creates and returns a SELECT query for records from $table and with conditions based on the configuration in the $conf array
5673
     * Implements the "select" function in TypoScript
5674
     *
5675
     * @param string $table See ->exec_getQuery()
5676
     * @param array $conf See ->exec_getQuery()
5677
     * @param bool $returnQueryArray If set, the function will return the query not as a string but array with the various parts. RECOMMENDED!
5678
     * @return mixed A SELECT query if $returnQueryArray is FALSE, otherwise the SELECT query in an array as parts.
5679
     * @throws \RuntimeException
5680
     * @throws \InvalidArgumentException
5681
     * @internal
5682
     * @see numRows()
5683
     */
5684
    public function getQuery($table, $conf, $returnQueryArray = false)
5685
    {
5686
        // Resolve stdWrap in these properties first
5687
        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
5688
        $properties = [
5689
            'pidInList',
5690
            'uidInList',
5691
            'languageField',
5692
            'selectFields',
5693
            'max',
5694
            'begin',
5695
            'groupBy',
5696
            'orderBy',
5697
            'join',
5698
            'leftjoin',
5699
            'rightjoin',
5700
            'recursive',
5701
            'where'
5702
        ];
5703
        foreach ($properties as $property) {
5704
            $conf[$property] = trim(
5705
                isset($conf[$property . '.'])
5706
                    ? $this->stdWrap($conf[$property], $conf[$property . '.'])
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
5707
                    : $conf[$property]
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
5708
            );
5709
            if ($conf[$property] === '') {
5710
                unset($conf[$property]);
5711
            } elseif (in_array($property, ['languageField', 'selectFields', 'join', 'leftJoin', 'rightJoin', 'where'], true)) {
5712
                $conf[$property] = QueryHelper::quoteDatabaseIdentifiers($connection, $conf[$property]);
5713
            }
5714
            if (isset($conf[$property . '.'])) {
5715
                // stdWrapping already done, so remove the sub-array
5716
                unset($conf[$property . '.']);
5717
            }
5718
        }
5719
        // Handle PDO-style named parameter markers first
5720
        $queryMarkers = $this->getQueryMarkers($table, $conf);
5721
        // Replace the markers in the non-stdWrap properties
5722
        foreach ($queryMarkers as $marker => $markerValue) {
5723
            $properties = [
5724
                'uidInList',
5725
                'selectFields',
5726
                'where',
5727
                'max',
5728
                'begin',
5729
                'groupBy',
5730
                'orderBy',
5731
                'join',
5732
                'leftjoin',
5733
                'rightjoin'
5734
            ];
5735
            foreach ($properties as $property) {
5736
                if ($conf[$property]) {
5737
                    $conf[$property] = str_replace('###' . $marker . '###', $markerValue, $conf[$property]);
5738
                }
5739
            }
5740
        }
5741
5742
        // Construct WHERE clause:
5743
        // Handle recursive function for the pidInList
5744
        if (isset($conf['recursive'])) {
5745
            $conf['recursive'] = (int)$conf['recursive'];
5746
            if ($conf['recursive'] > 0) {
5747
                $pidList = GeneralUtility::trimExplode(',', $conf['pidInList'], true);
5748
                array_walk($pidList, function (&$storagePid) {
5749
                    if ($storagePid === 'this') {
5750
                        $storagePid = $this->getTypoScriptFrontendController()->id;
5751
                    }
5752
                    if ($storagePid > 0) {
5753
                        $storagePid = -$storagePid;
5754
                    }
5755
                });
5756
                $expandedPidList = [];
5757
                foreach ($pidList as $value) {
5758
                    // Implementation of getTreeList allows to pass the id negative to include
5759
                    // it into the result otherwise only childpages are returned
5760
                    $expandedPidList = array_merge(
5761
                        GeneralUtility::intExplode(',', $this->getTreeList($value, $conf['recursive'])),
5762
                        $expandedPidList
5763
                    );
5764
                }
5765
                $conf['pidInList'] = implode(',', $expandedPidList);
5766
            }
5767
        }
5768
        if ((string)$conf['pidInList'] === '') {
5769
            $conf['pidInList'] = 'this';
5770
        }
5771
5772
        $queryParts = $this->getQueryConstraints($table, $conf);
5773
5774
        $queryBuilder = $connection->createQueryBuilder();
5775
        // @todo Check against getQueryConstraints, can probably use FrontendRestrictions
5776
        // @todo here and remove enableFields there.
5777
        $queryBuilder->getRestrictions()->removeAll();
5778
        $queryBuilder->select('*')->from($table);
5779
5780
        if ($queryParts['where']) {
5781
            $queryBuilder->where($queryParts['where']);
5782
        }
5783
5784
        if ($queryParts['groupBy']) {
5785
            $queryBuilder->groupBy(...$queryParts['groupBy']);
5786
        }
5787
5788
        if (is_array($queryParts['orderBy'])) {
5789
            foreach ($queryParts['orderBy'] as $orderBy) {
5790
                $queryBuilder->addOrderBy(...$orderBy);
5791
            }
5792
        }
5793
5794
        // Fields:
5795
        if ($conf['selectFields']) {
5796
            $queryBuilder->selectLiteral($this->sanitizeSelectPart($conf['selectFields'], $table));
5797
        }
5798
5799
        // Setting LIMIT:
5800
        $error = false;
5801
        if ($conf['max'] || $conf['begin']) {
5802
            // Finding the total number of records, if used:
5803
            if (strpos(strtolower($conf['begin'] . $conf['max']), 'total') !== false) {
5804
                $countQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
5805
                $countQueryBuilder->getRestrictions()->removeAll();
5806
                $countQueryBuilder->count('*')
5807
                    ->from($table)
5808
                    ->where($queryParts['where']);
5809
5810
                if ($queryParts['groupBy']) {
5811
                    $countQueryBuilder->groupBy(...$queryParts['groupBy']);
5812
                }
5813
5814
                try {
5815
                    $count = $countQueryBuilder->execute()->fetchColumn(0);
5816
                    $conf['max'] = str_ireplace('total', $count, $conf['max']);
5817
                    $conf['begin'] = str_ireplace('total', $count, $conf['begin']);
5818
                } catch (DBALException $e) {
5819
                    $this->getTimeTracker()->setTSlogMessage($e->getPrevious()->getMessage());
5820
                    $error = true;
5821
                }
5822
            }
5823
5824
            if (!$error) {
5825
                $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

5825
                $conf['begin'] = MathUtility::forceIntegerInRange(/** @scrutinizer ignore-type */ ceil($this->calc($conf['begin'])), 0);
Loading history...
5826
                $conf['max'] = MathUtility::forceIntegerInRange(ceil($this->calc($conf['max'])), 0);
5827
                if ($conf['begin'] > 0) {
5828
                    $queryBuilder->setFirstResult($conf['begin']);
5829
                }
5830
                $queryBuilder->setMaxResults($conf['max'] ?: 100000);
5831
            }
5832
        }
5833
5834
        if (!$error) {
5835
            // Setting up tablejoins:
5836
            if ($conf['join']) {
5837
                $joinParts = QueryHelper::parseJoin($conf['join']);
5838
                $queryBuilder->join(
5839
                    $table,
5840
                    $joinParts['tableName'],
5841
                    $joinParts['tableAlias'],
5842
                    $joinParts['joinCondition']
5843
                );
5844
            } elseif ($conf['leftjoin']) {
5845
                $joinParts = QueryHelper::parseJoin($conf['leftjoin']);
5846
                $queryBuilder->leftJoin(
5847
                    $table,
5848
                    $joinParts['tableName'],
5849
                    $joinParts['tableAlias'],
5850
                    $joinParts['joinCondition']
5851
                );
5852
            } elseif ($conf['rightjoin']) {
5853
                $joinParts = QueryHelper::parseJoin($conf['rightjoin']);
5854
                $queryBuilder->rightJoin(
5855
                    $table,
5856
                    $joinParts['tableName'],
5857
                    $joinParts['tableAlias'],
5858
                    $joinParts['joinCondition']
5859
                );
5860
            }
5861
5862
            // Convert the QueryBuilder object into a SQL statement.
5863
            $query = $queryBuilder->getSQL();
5864
5865
            // Replace the markers in the queryParts to handle stdWrap enabled properties
5866
            foreach ($queryMarkers as $marker => $markerValue) {
5867
                // @todo Ugly hack that needs to be cleaned up, with the current architecture
5868
                // @todo for exec_Query / getQuery it's the best we can do.
5869
                $query = str_replace('###' . $marker . '###', $markerValue, $query);
5870
                foreach ($queryParts as $queryPartKey => &$queryPartValue) {
5871
                    $queryPartValue = str_replace('###' . $marker . '###', $markerValue, $queryPartValue);
5872
                }
5873
                unset($queryPartValue);
5874
            }
5875
5876
            return $returnQueryArray ? $this->getQueryArray($queryBuilder) : $query;
5877
        }
5878
5879
        return '';
5880
    }
5881
5882
    /**
5883
     * Helper to transform a QueryBuilder object into a queryParts array that can be used
5884
     * with exec_SELECT_queryArray
5885
     *
5886
     * @param \TYPO3\CMS\Core\Database\Query\QueryBuilder $queryBuilder
5887
     * @return array
5888
     * @throws \RuntimeException
5889
     */
5890
    protected function getQueryArray(QueryBuilder $queryBuilder)
5891
    {
5892
        $fromClauses = [];
5893
        $knownAliases = [];
5894
        $queryParts = [];
5895
5896
        // Loop through all FROM clauses
5897
        foreach ($queryBuilder->getQueryPart('from') as $from) {
5898
            if ($from['alias'] === null) {
5899
                $tableSql = $from['table'];
5900
                $tableReference = $from['table'];
5901
            } else {
5902
                $tableSql = $from['table'] . ' ' . $from['alias'];
5903
                $tableReference = $from['alias'];
5904
            }
5905
5906
            $knownAliases[$tableReference] = true;
5907
5908
            $fromClauses[$tableReference] = $tableSql . $this->getQueryArrayJoinHelper(
5909
                $tableReference,
5910
                $queryBuilder->getQueryPart('join'),
5911
                $knownAliases
5912
            );
5913
        }
5914
5915
        $queryParts['SELECT'] = implode(', ', $queryBuilder->getQueryPart('select'));
5916
        $queryParts['FROM'] = implode(', ', $fromClauses);
5917
        $queryParts['WHERE'] = (string)$queryBuilder->getQueryPart('where') ?: '';
5918
        $queryParts['GROUPBY'] = implode(', ', $queryBuilder->getQueryPart('groupBy'));
5919
        $queryParts['ORDERBY'] = implode(', ', $queryBuilder->getQueryPart('orderBy'));
5920
        if ($queryBuilder->getFirstResult() > 0) {
5921
            $queryParts['LIMIT'] = $queryBuilder->getFirstResult() . ',' . $queryBuilder->getMaxResults();
5922
        } elseif ($queryBuilder->getMaxResults() > 0) {
5923
            $queryParts['LIMIT'] = $queryBuilder->getMaxResults();
5924
        }
5925
5926
        return $queryParts;
5927
    }
5928
5929
    /**
5930
     * Helper to transform the QueryBuilder join part into a SQL fragment.
5931
     *
5932
     * @param string $fromAlias
5933
     * @param array $joinParts
5934
     * @param array $knownAliases
5935
     * @return string
5936
     * @throws \RuntimeException
5937
     */
5938
    protected function getQueryArrayJoinHelper(string $fromAlias, array $joinParts, array &$knownAliases): string
5939
    {
5940
        $sql = '';
5941
5942
        if (isset($joinParts['join'][$fromAlias])) {
5943
            foreach ($joinParts['join'][$fromAlias] as $join) {
5944
                if (array_key_exists($join['joinAlias'], $knownAliases)) {
5945
                    throw new \RuntimeException(
5946
                        'Non unique join alias: "' . $join['joinAlias'] . '" found.',
5947
                        1472748872
5948
                    );
5949
                }
5950
                $sql .= ' ' . strtoupper($join['joinType'])
5951
                    . ' JOIN ' . $join['joinTable'] . ' ' . $join['joinAlias']
5952
                    . ' ON ' . ((string)$join['joinCondition']);
5953
                $knownAliases[$join['joinAlias']] = true;
5954
            }
5955
5956
            foreach ($joinParts['join'][$fromAlias] as $join) {
5957
                $sql .= $this->getQueryArrayJoinHelper($join['joinAlias'], $joinParts, $knownAliases);
5958
            }
5959
        }
5960
5961
        return $sql;
5962
    }
5963
    /**
5964
     * Helper function for getQuery(), creating the WHERE clause of the SELECT query
5965
     *
5966
     * @param string $table The table name
5967
     * @param array $conf The TypoScript configuration properties
5968
     * @return array Associative array containing the prepared data for WHERE, ORDER BY and GROUP BY fragments
5969
     * @throws \InvalidArgumentException
5970
     * @see getQuery()
5971
     */
5972
    protected function getQueryConstraints(string $table, array $conf): array
5973
    {
5974
        // Init:
5975
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
5976
        $expressionBuilder = $queryBuilder->expr();
5977
        $tsfe = $this->getTypoScriptFrontendController();
5978
        $constraints = [];
5979
        $pid_uid_flag = 0;
5980
        $enableFieldsIgnore = [];
5981
        $queryParts = [
5982
            'where' => null,
5983
            'groupBy' => null,
5984
            'orderBy' => null,
5985
        ];
5986
5987
        $isInWorkspace = GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('workspace', 'isOffline');
5988
        $considerMovePlaceholders = (
5989
            $isInWorkspace && $table !== 'pages'
5990
            && !empty($GLOBALS['TCA'][$table]['ctrl']['versioningWS'])
5991
        );
5992
5993
        if (trim($conf['uidInList'])) {
5994
            $listArr = GeneralUtility::intExplode(',', str_replace('this', $tsfe->contentPid, $conf['uidInList']));
5995
5996
            // If move placeholder shall be considered, select via t3ver_move_id
5997
            if ($considerMovePlaceholders) {
5998
                $constraints[] = (string)$expressionBuilder->orX(
5999
                    $expressionBuilder->in($table . '.uid', $listArr),
6000
                    $expressionBuilder->andX(
6001
                        $expressionBuilder->eq(
6002
                            $table . '.t3ver_state',
6003
                            (int)(string)VersionState::cast(VersionState::MOVE_PLACEHOLDER)
6004
                        ),
6005
                        $expressionBuilder->in($table . '.t3ver_move_id', $listArr)
6006
                    )
6007
                );
6008
            } else {
6009
                $constraints[] = (string)$expressionBuilder->in($table . '.uid', $listArr);
6010
            }
6011
            $pid_uid_flag++;
6012
        }
6013
6014
        // Static_* tables are allowed to be fetched from root page
6015
        if (strpos($table, 'static_') === 0) {
6016
            $pid_uid_flag++;
6017
        }
6018
6019
        if (trim($conf['pidInList'])) {
6020
            $listArr = GeneralUtility::intExplode(',', str_replace('this', $tsfe->contentPid, $conf['pidInList']));
6021
            // Removes all pages which are not visible for the user!
6022
            $listArr = $this->checkPidArray($listArr);
6023
            if (GeneralUtility::inList($conf['pidInList'], 'root')) {
6024
                $listArr[] = 0;
6025
            }
6026
            if (GeneralUtility::inList($conf['pidInList'], '-1')) {
6027
                $listArr[] = -1;
6028
                $enableFieldsIgnore['pid'] = true;
6029
            }
6030
            if (!empty($listArr)) {
6031
                $constraints[] = $expressionBuilder->in($table . '.pid', array_map('intval', $listArr));
6032
                $pid_uid_flag++;
6033
            } else {
6034
                // If not uid and not pid then uid is set to 0 - which results in nothing!!
6035
                $pid_uid_flag = 0;
6036
            }
6037
        }
6038
6039
        // If not uid and not pid then uid is set to 0 - which results in nothing!!
6040
        if (!$pid_uid_flag) {
6041
            $constraints[] = $expressionBuilder->eq($table . '.uid', 0);
6042
        }
6043
6044
        $where = trim($this->stdWrapValue('where', $conf));
6045
        if ($where) {
6046
            $constraints[] = QueryHelper::stripLogicalOperatorPrefix($where);
6047
        }
6048
6049
        // Check if the default language should be fetched (= doing overlays), or if only the records of a language should be fetched
6050
        // but only do this for TCA tables that have languages enabled
6051
        $languageConstraint = $this->getLanguageRestriction($expressionBuilder, $table, $conf, GeneralUtility::makeInstance(Context::class));
6052
        if ($languageConstraint !== null) {
6053
            $constraints[] = $languageConstraint;
6054
        }
6055
6056
        // Enablefields
6057
        if ($table === 'pages') {
6058
            $constraints[] = QueryHelper::stripLogicalOperatorPrefix($tsfe->sys_page->where_hid_del);
6059
            $constraints[] = QueryHelper::stripLogicalOperatorPrefix($tsfe->sys_page->where_groupAccess);
6060
        } else {
6061
            $constraints[] = QueryHelper::stripLogicalOperatorPrefix($tsfe->sys_page->enableFields($table, -1, $enableFieldsIgnore));
6062
        }
6063
6064
        // MAKE WHERE:
6065
        if (count($constraints) !== 0) {
6066
            $queryParts['where'] = $expressionBuilder->andX(...$constraints);
6067
        }
6068
        // GROUP BY
6069
        $groupBy = trim($this->stdWrapValue('groupBy', $conf));
6070
        if ($groupBy) {
6071
            $queryParts['groupBy'] = QueryHelper::parseGroupBy($groupBy);
6072
        }
6073
6074
        // ORDER BY
6075
        $orderByString = trim($this->stdWrapValue('orderBy', $conf));
6076
        if ($orderByString) {
6077
            $queryParts['orderBy'] = QueryHelper::parseOrderBy($orderByString);
6078
        }
6079
6080
        // Return result:
6081
        return $queryParts;
6082
    }
6083
6084
    /**
6085
     * Adds parts to the WHERE clause that are related to language.
6086
     * This only works on TCA tables which have the [ctrl][languageField] field set or if they
6087
     * have select.languageField = my_language_field set explicitly.
6088
     *
6089
     * It is also possible to disable the language restriction for a query by using select.languageField = 0,
6090
     * if select.languageField is not explicitly set, the TCA default values are taken.
6091
     *
6092
     * If the table is "localizeable" (= any of the criteria above is met), then the DB query is restricted:
6093
     *
6094
     * If the current language aspect has overlays enabled, then the only records with language "0" or "-1" are
6095
     * fetched (the overlays are taken care of later-on).
6096
     * if the current language has overlays but also records without localization-parent (free mode) available,
6097
     * then these are fetched as well. This can explicitly set via select.includeRecordsWithoutDefaultTranslation = 1
6098
     * which overrules the overlayType within the language aspect.
6099
     *
6100
     * If the language aspect has NO overlays enabled, it behaves as in "free mode" (= only fetch the records
6101
     * for the current language.
6102
     *
6103
     * @param ExpressionBuilder $expressionBuilder
6104
     * @param string $table
6105
     * @param array $conf
6106
     * @param Context $context
6107
     * @return string|\TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression|null
6108
     * @throws \TYPO3\CMS\Core\Context\Exception\AspectNotFoundException
6109
     */
6110
    protected function getLanguageRestriction(ExpressionBuilder $expressionBuilder, string $table, array $conf, Context $context)
6111
    {
6112
        $languageField = '';
6113
        $localizationParentField = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] ?? null;
6114
        // Check if the table is translatable, and set the language field by default from the TCA information
6115
        if (!empty($conf['languageField']) || !isset($conf['languageField'])) {
6116
            if (isset($conf['languageField']) && !empty($GLOBALS['TCA'][$table]['columns'][$conf['languageField']])) {
6117
                $languageField = $conf['languageField'];
6118
            } elseif (!empty($GLOBALS['TCA'][$table]['ctrl']['languageField']) && !empty($localizationParentField)) {
6119
                $languageField = $table . '.' . $GLOBALS['TCA'][$table]['ctrl']['languageField'];
6120
            }
6121
        }
6122
6123
        // No language restriction enabled explicitly or available via TCA
6124
        if (empty($languageField)) {
6125
            return null;
6126
        }
6127
6128
        /** @var LanguageAspect $languageAspect */
6129
        $languageAspect = $context->getAspect('language');
6130
        if ($languageAspect->doOverlays() && !empty($localizationParentField)) {
6131
            // Sys language content is set to zero/-1 - and it is expected that whatever routine processes the output will
6132
            // OVERLAY the records with localized versions!
6133
            $languageQuery = $expressionBuilder->in($languageField, [0, -1]);
6134
            // Use this option to include records that don't have a default language counterpart ("free mode")
6135
            // (originalpointerfield is 0 and the language field contains the requested language)
6136
            if (isset($conf['includeRecordsWithoutDefaultTranslation']) || $conf['includeRecordsWithoutDefaultTranslation.']) {
6137
                $includeRecordsWithoutDefaultTranslation = isset($conf['includeRecordsWithoutDefaultTranslation.']) ?
6138
                    $this->stdWrap($conf['includeRecordsWithoutDefaultTranslation'], $conf['includeRecordsWithoutDefaultTranslation.']) : $conf['includeRecordsWithoutDefaultTranslation'];
6139
                $includeRecordsWithoutDefaultTranslation = trim($includeRecordsWithoutDefaultTranslation) !== '';
6140
            } else {
6141
                // Option was not explicitly set, check what's in for the language overlay type.
6142
                $includeRecordsWithoutDefaultTranslation = $languageAspect->getOverlayType() === $languageAspect::OVERLAYS_ON_WITH_FLOATING;
6143
            }
6144
            if ($includeRecordsWithoutDefaultTranslation) {
6145
                $languageQuery = $expressionBuilder->orX(
6146
                    $languageQuery,
6147
                    $expressionBuilder->andX(
6148
                        $expressionBuilder->eq($table . '.' . $localizationParentField, 0),
6149
                        $expressionBuilder->eq($languageField, $languageAspect->getContentId())
6150
                    )
6151
                );
6152
            }
6153
            return $languageQuery;
6154
        }
6155
        // No overlays = only fetch records given for the requested language and "all languages"
6156
        return $expressionBuilder->in($languageField, [$languageAspect->getContentId(), -1]);
6157
    }
6158
6159
    /**
6160
     * Helper function for getQuery, sanitizing the select part
6161
     *
6162
     * This functions checks if the necessary fields are part of the select
6163
     * and adds them if necessary.
6164
     *
6165
     * @param string $selectPart Select part
6166
     * @param string $table Table to select from
6167
     * @return string Sanitized select part
6168
     * @internal
6169
     * @see getQuery
6170
     */
6171
    protected function sanitizeSelectPart($selectPart, $table)
6172
    {
6173
        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
6174
6175
        // Pattern matching parts
6176
        $matchStart = '/(^\\s*|,\\s*|' . $table . '\\.)';
6177
        $matchEnd = '(\\s*,|\\s*$)/';
6178
        $necessaryFields = ['uid', 'pid'];
6179
        $wsFields = ['t3ver_state'];
6180
        if (isset($GLOBALS['TCA'][$table]) && !preg_match($matchStart . '\\*' . $matchEnd, $selectPart) && !preg_match('/(count|max|min|avg|sum)\\([^\\)]+\\)|distinct/i', $selectPart)) {
6181
            foreach ($necessaryFields as $field) {
6182
                $match = $matchStart . $field . $matchEnd;
6183
                if (!preg_match($match, $selectPart)) {
6184
                    $selectPart .= ', ' . $connection->quoteIdentifier($table . '.' . $field) . ' AS ' . $connection->quoteIdentifier($field);
6185
                }
6186
            }
6187
            if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
6188
                foreach ($wsFields as $field) {
6189
                    $match = $matchStart . $field . $matchEnd;
6190
                    if (!preg_match($match, $selectPart)) {
6191
                        $selectPart .= ', ' . $connection->quoteIdentifier($table . '.' . $field) . ' AS ' . $connection->quoteIdentifier($field);
6192
                    }
6193
                }
6194
            }
6195
        }
6196
        return $selectPart;
6197
    }
6198
6199
    /**
6200
     * 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)
6201
     *
6202
     * @param int[] $pageIds Array of Page UID numbers for select and for which pages with enablefields and bad doktypes should be removed.
6203
     * @return array Returns the array of remaining page UID numbers
6204
     * @internal
6205
     */
6206
    public function checkPidArray($pageIds)
6207
    {
6208
        if (!is_array($pageIds) || empty($pageIds)) {
0 ignored issues
show
introduced by
The condition is_array($pageIds) is always true.
Loading history...
6209
            return [];
6210
        }
6211
        $restrictionContainer = GeneralUtility::makeInstance(FrontendRestrictionContainer::class);
6212
        $restrictionContainer->add(GeneralUtility::makeInstance(
6213
            DocumentTypeExclusionRestriction::class,
6214
            GeneralUtility::intExplode(',', $this->checkPid_badDoktypeList, true)
6215
        ));
6216
        return $this->getTypoScriptFrontendController()->sys_page->filterAccessiblePageIds($pageIds, $restrictionContainer);
6217
    }
6218
6219
    /**
6220
     * Builds list of marker values for handling PDO-like parameter markers in select parts.
6221
     * 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.
6222
     *
6223
     * @param string $table Table to select records from
6224
     * @param array $conf Select part of CONTENT definition
6225
     * @return array List of values to replace markers with
6226
     * @internal
6227
     * @see getQuery()
6228
     */
6229
    public function getQueryMarkers($table, $conf)
6230
    {
6231
        if (!is_array($conf['markers.'])) {
6232
            return [];
6233
        }
6234
        // Parse markers and prepare their values
6235
        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
6236
        $markerValues = [];
6237
        foreach ($conf['markers.'] as $dottedMarker => $dummy) {
6238
            $marker = rtrim($dottedMarker, '.');
6239
            if ($dottedMarker != $marker . '.') {
6240
                continue;
6241
            }
6242
            // Parse definition
6243
            // todo else value is always null
6244
            $tempValue = isset($conf['markers.'][$dottedMarker])
6245
                ? $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...
6246
                : $conf['markers.'][$dottedMarker]['value'];
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
6247
            // Quote/escape if needed
6248
            if (is_numeric($tempValue)) {
6249
                if ((int)$tempValue == $tempValue) {
6250
                    // Handle integer
6251
                    $markerValues[$marker] = (int)$tempValue;
6252
                } else {
6253
                    // Handle float
6254
                    $markerValues[$marker] = (float)$tempValue;
6255
                }
6256
            } elseif ($tempValue === null) {
6257
                // It represents NULL
6258
                $markerValues[$marker] = 'NULL';
6259
            } elseif (!empty($conf['markers.'][$dottedMarker]['commaSeparatedList'])) {
6260
                // See if it is really a comma separated list of values
6261
                $explodeValues = GeneralUtility::trimExplode(',', $tempValue);
6262
                if (count($explodeValues) > 1) {
6263
                    // Handle each element of list separately
6264
                    $tempArray = [];
6265
                    foreach ($explodeValues as $listValue) {
6266
                        if (is_numeric($listValue)) {
6267
                            if ((int)$listValue == $listValue) {
6268
                                $tempArray[] = (int)$listValue;
6269
                            } else {
6270
                                $tempArray[] = (float)$listValue;
6271
                            }
6272
                        } else {
6273
                            // If quoted, remove quotes before
6274
                            // escaping.
6275
                            if (preg_match('/^\'([^\']*)\'$/', $listValue, $matches)) {
6276
                                $listValue = $matches[1];
6277
                            } elseif (preg_match('/^\\"([^\\"]*)\\"$/', $listValue, $matches)) {
6278
                                $listValue = $matches[1];
6279
                            }
6280
                            $tempArray[] = $connection->quote($listValue);
6281
                        }
6282
                    }
6283
                    $markerValues[$marker] = implode(',', $tempArray);
6284
                } else {
6285
                    // Handle remaining values as string
6286
                    $markerValues[$marker] = $connection->quote($tempValue);
6287
                }
6288
            } else {
6289
                // Handle remaining values as string
6290
                $markerValues[$marker] = $connection->quote($tempValue);
6291
            }
6292
        }
6293
        return $markerValues;
6294
    }
6295
6296
    /***********************************************
6297
     *
6298
     * Frontend editing functions
6299
     *
6300
     ***********************************************/
6301
    /**
6302
     * 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.
6303
     * With the "edit panel" the user will see buttons with links to editing, moving, hiding, deleting the element
6304
     * This function is used for the cObject EDITPANEL and the stdWrap property ".editPanel"
6305
     *
6306
     * @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.
6307
     * @param array $conf TypoScript configuration properties for the editPanel
6308
     * @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
6309
     * @param array $dataArray Alternative data array to use. Default is $this->data
6310
     * @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.
6311
     */
6312
    public function editPanel($content, $conf, $currentRecord = '', $dataArray = [])
6313
    {
6314
        if (!$this->getTypoScriptFrontendController()->isBackendUserLoggedIn()) {
6315
            return $content;
6316
        }
6317
        if (!$this->getTypoScriptFrontendController()->displayEditIcons) {
6318
            return $content;
6319
        }
6320
6321
        if (!$currentRecord) {
6322
            $currentRecord = $this->currentRecord;
6323
        }
6324
        if (empty($dataArray)) {
6325
            $dataArray = $this->data;
6326
        }
6327
6328
        if ($conf['newRecordFromTable']) {
6329
            $currentRecord = $conf['newRecordFromTable'] . ':NEW';
6330
            $conf['allow'] = 'new';
6331
            $checkEditAccessInternals = false;
6332
        } else {
6333
            $checkEditAccessInternals = true;
6334
        }
6335
        [$table, $uid] = explode(':', $currentRecord);
6336
        // Page ID for new records, 0 if not specified
6337
        $newRecordPid = (int)$conf['newRecordInPid'];
6338
        $newUid = null;
6339
        if (!$conf['onlyCurrentPid'] || $dataArray['pid'] == $this->getTypoScriptFrontendController()->id) {
6340
            if ($table === 'pages') {
6341
                $newUid = $uid;
6342
            } else {
6343
                if ($conf['newRecordFromTable']) {
6344
                    $newUid = $this->getTypoScriptFrontendController()->id;
6345
                    if ($newRecordPid) {
6346
                        $newUid = $newRecordPid;
6347
                    }
6348
                } else {
6349
                    $newUid = -1 * $uid;
6350
                }
6351
            }
6352
        }
6353
        if ($table && $this->getFrontendBackendUser()->allowedToEdit($table, $dataArray, $conf, $checkEditAccessInternals) && $this->getFrontendBackendUser()->allowedToEditLanguage($table, $dataArray)) {
6354
            $editClass = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/classes/class.frontendedit.php']['edit'];
6355
            if ($editClass) {
6356
                $edit = GeneralUtility::makeInstance($editClass);
6357
                $allowedActions = $this->getFrontendBackendUser()->getAllowedEditActions($table, $conf, $dataArray['pid']);
6358
                $content = $edit->editPanel($content, $conf, $currentRecord, $dataArray, $table, $allowedActions, $newUid, []);
6359
            }
6360
        }
6361
        return $content;
6362
    }
6363
6364
    /**
6365
     * 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.
6366
     * This implements TYPO3 context sensitive editing facilities. Only backend users will have access (if properly configured as well).
6367
     *
6368
     * @param string $content The content to which the edit icons should be appended
6369
     * @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
6370
     * @param array $conf TypoScript properties for configuring the edit icons.
6371
     * @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
6372
     * @param array $dataArray Alternative data array to use. Default is $this->data
6373
     * @param string $addUrlParamStr Additional URL parameters for the link pointing to FormEngine
6374
     * @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.
6375
     */
6376
    public function editIcons($content, $params, array $conf = [], $currentRecord = '', $dataArray = [], $addUrlParamStr = '')
6377
    {
6378
        if (!$this->getTypoScriptFrontendController()->isBackendUserLoggedIn()) {
6379
            return $content;
6380
        }
6381
        if (!$this->getTypoScriptFrontendController()->displayFieldEditIcons) {
6382
            return $content;
6383
        }
6384
        if (!$currentRecord) {
6385
            $currentRecord = $this->currentRecord;
6386
        }
6387
        if (empty($dataArray)) {
6388
            $dataArray = $this->data;
6389
        }
6390
        // Check incoming params:
6391
        [$currentRecordTable, $currentRecordUID] = explode(':', $currentRecord);
6392
        [$fieldList, $table] = array_reverse(GeneralUtility::trimExplode(':', $params, true));
6393
        // Reverse the array because table is optional
6394
        if (!$table) {
6395
            $table = $currentRecordTable;
6396
        } elseif ($table != $currentRecordTable) {
6397
            // If the table is set as the first parameter, and does not match the table of the current record, then just return.
6398
            return $content;
6399
        }
6400
6401
        $editUid = $dataArray['_LOCALIZED_UID'] ?: $currentRecordUID;
6402
        // Edit icons imply that the editing action is generally allowed, assuming page and content element permissions permit it.
6403
        if (!array_key_exists('allow', $conf)) {
6404
            $conf['allow'] = 'edit';
6405
        }
6406
        if ($table && $this->getFrontendBackendUser()->allowedToEdit($table, $dataArray, $conf, true) && $fieldList && $this->getFrontendBackendUser()->allowedToEditLanguage($table, $dataArray)) {
6407
            $editClass = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/classes/class.frontendedit.php']['edit'];
6408
            if ($editClass) {
6409
                $edit = GeneralUtility::makeInstance($editClass);
6410
                $content = $edit->editIcons($content, $params, $conf, $currentRecord, $dataArray, $addUrlParamStr, $table, $editUid, $fieldList);
6411
            }
6412
        }
6413
        return $content;
6414
    }
6415
6416
    /**
6417
     * Returns TRUE if the input table/row would be hidden in the frontend (according nto the current time and simulate user group)
6418
     *
6419
     * @param string $table The table name
6420
     * @param array $row The data record
6421
     * @return bool
6422
     * @internal
6423
     * @see editPanelPreviewBorder()
6424
     */
6425
    public function isDisabled($table, $row)
6426
    {
6427
        $tsfe = $this->getTypoScriptFrontendController();
6428
        $enablecolumns = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns'];
6429
        return $enablecolumns['disabled'] && $row[$enablecolumns['disabled']]
6430
            || $enablecolumns['fe_group'] && $tsfe->simUserGroup && (int)$row[$enablecolumns['fe_group']] === (int)$tsfe->simUserGroup
6431
            || $enablecolumns['starttime'] && $row[$enablecolumns['starttime']] > $GLOBALS['EXEC_TIME']
6432
            || $enablecolumns['endtime'] && $row[$enablecolumns['endtime']] && $row[$enablecolumns['endtime']] < $GLOBALS['EXEC_TIME'];
6433
    }
6434
6435
    /**
6436
     * Get instance of FAL resource factory
6437
     *
6438
     * @return ResourceFactory
6439
     */
6440
    protected function getResourceFactory()
6441
    {
6442
        return GeneralUtility::makeInstance(ResourceFactory::class);
6443
    }
6444
6445
    /**
6446
     * Wrapper function for GeneralUtility::getIndpEnv()
6447
     *
6448
     * @see GeneralUtility::getIndpEnv
6449
     * @param string $key Name of the "environment variable"/"server variable" you wish to get.
6450
     * @return string
6451
     */
6452
    protected function getEnvironmentVariable($key)
6453
    {
6454
        return GeneralUtility::getIndpEnv($key);
6455
    }
6456
6457
    /**
6458
     * Fetches content from cache
6459
     *
6460
     * @param array $configuration Array
6461
     * @return string|bool FALSE on cache miss
6462
     * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
6463
     */
6464
    protected function getFromCache(array $configuration)
6465
    {
6466
        $content = false;
6467
6468
        if ($this->getTypoScriptFrontendController()->no_cache) {
6469
            return $content;
6470
        }
6471
        $cacheKey = $this->calculateCacheKey($configuration);
6472
        if (!empty($cacheKey)) {
6473
            /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cacheFrontend */
6474
            $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)
6475
                ->getCache('hash');
6476
            $content = $cacheFrontend->get($cacheKey);
6477
        }
6478
        return $content;
6479
    }
6480
6481
    /**
6482
     * Calculates the lifetime of a cache entry based on the given configuration
6483
     *
6484
     * @param array $configuration
6485
     * @return int|null
6486
     */
6487
    protected function calculateCacheLifetime(array $configuration)
6488
    {
6489
        $configuration['lifetime'] = $configuration['lifetime'] ?? '';
6490
        $lifetimeConfiguration = $this->stdWrapValue('lifetime', $configuration);
6491
6492
        $lifetime = null; // default lifetime
6493
        if (strtolower($lifetimeConfiguration) === 'unlimited') {
6494
            $lifetime = 0; // unlimited
6495
        } elseif ($lifetimeConfiguration > 0) {
6496
            $lifetime = (int)$lifetimeConfiguration; // lifetime in seconds
6497
        }
6498
        return $lifetime;
6499
    }
6500
6501
    /**
6502
     * Calculates the tags for a cache entry bases on the given configuration
6503
     *
6504
     * @param array $configuration
6505
     * @return array
6506
     */
6507
    protected function calculateCacheTags(array $configuration)
6508
    {
6509
        $configuration['tags'] = $configuration['tags'] ?? '';
6510
        $tags = $this->stdWrapValue('tags', $configuration);
6511
        return empty($tags) ? [] : GeneralUtility::trimExplode(',', $tags);
6512
    }
6513
6514
    /**
6515
     * Applies stdWrap to the cache key
6516
     *
6517
     * @param array $configuration
6518
     * @return string
6519
     */
6520
    protected function calculateCacheKey(array $configuration)
6521
    {
6522
        $configuration['key'] = $configuration['key'] ?? '';
6523
        return $this->stdWrapValue('key', $configuration);
6524
    }
6525
6526
    /**
6527
     * Returns the current BE user.
6528
     *
6529
     * @return \TYPO3\CMS\Backend\FrontendBackendUserAuthentication
6530
     */
6531
    protected function getFrontendBackendUser()
6532
    {
6533
        return $GLOBALS['BE_USER'];
6534
    }
6535
6536
    /**
6537
     * @return TimeTracker
6538
     */
6539
    protected function getTimeTracker()
6540
    {
6541
        return GeneralUtility::makeInstance(TimeTracker::class);
6542
    }
6543
6544
    /**
6545
     * @return \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
6546
     */
6547
    protected function getTypoScriptFrontendController()
6548
    {
6549
        return $this->typoScriptFrontendController ?: $GLOBALS['TSFE'];
6550
    }
6551
6552
    /**
6553
     * Support anchors without href value
6554
     * Changes ContentObjectRenderer::typolink to render a tag without href,
6555
     * if id or name attribute is present.
6556
     *
6557
     * @param string $linkText
6558
     * @param array $conf Typolink configuration decoded as array
6559
     * @return string Full a-Tag or just the linktext if id or name are not set.
6560
     */
6561
    protected function resolveAnchorLink(string $linkText, array $conf): string
6562
    {
6563
        $anchorTag = '<a ' . $this->getATagParams($conf) . '>';
6564
        $aTagParams = GeneralUtility::get_tag_attributes($anchorTag);
6565
        // If it looks like a anchor tag, render it anyway
6566
        if (isset($aTagParams['id']) || isset($aTagParams['name'])) {
6567
            return $anchorTag . $linkText . '</a>';
6568
        }
6569
        // Otherwise just return the link text
6570
        return $linkText;
6571
    }
6572
6573
    /**
6574
     * Get content length of the current tag that could also contain nested tag contents
6575
     *
6576
     * @param string $theValue
6577
     * @param int $pointer
6578
     * @param string $currentTag
6579
     * @return int
6580
     */
6581
    protected function getContentLengthOfCurrentTag(string $theValue, int $pointer, string $currentTag): int
6582
    {
6583
        $tempContent = strtolower(substr($theValue, $pointer));
6584
        $startTag = '<' . $currentTag;
6585
        $endTag = '</' . $currentTag . '>';
6586
        $offsetCount = 0;
6587
6588
        // Take care for nested tags
6589
        do {
6590
            $nextMatchingEndTagPosition = strpos($tempContent, $endTag);
6591
            // only match tag `a` in `<a href"...">` but not in `<abbr>`
6592
            $nextSameTypeTagPosition = preg_match(
6593
                '#' . $startTag . '[\s/>]#',
6594
                $tempContent,
6595
                $nextSameStartTagMatches,
6596
                PREG_OFFSET_CAPTURE
6597
            ) ? $nextSameStartTagMatches[0][1] : false;
6598
6599
            // filter out nested tag contents to help getting the correct closing tag
6600
            if ($nextSameTypeTagPosition !== false && $nextSameTypeTagPosition < $nextMatchingEndTagPosition) {
6601
                $lastOpeningTagStartPosition = strrpos(substr($tempContent, 0, $nextMatchingEndTagPosition), $startTag);
6602
                $closingTagEndPosition = $nextMatchingEndTagPosition + strlen($endTag);
6603
                $offsetCount += $closingTagEndPosition - $lastOpeningTagStartPosition;
6604
6605
                // replace content from latest tag start to latest tag end
6606
                $tempContent = substr($tempContent, 0, $lastOpeningTagStartPosition) . substr($tempContent, $closingTagEndPosition);
6607
            }
6608
        } while (
6609
            ($nextMatchingEndTagPosition !== false && $nextSameTypeTagPosition !== false) &&
6610
            $nextSameTypeTagPosition < $nextMatchingEndTagPosition
6611
        );
6612
6613
        // if no closing tag is found we use length of the whole content
6614
        $endingOffset = strlen($tempContent);
6615
        if ($nextMatchingEndTagPosition !== false) {
6616
            $endingOffset = $nextMatchingEndTagPosition + $offsetCount;
6617
        }
6618
6619
        return $endingOffset;
6620
    }
6621
}
6622