Completed
Push — master ( b3bd3c...fbeb16 )
by
unknown
26:22 queued 12:30
created

ContentObjectRenderer::stdWrap_htmlSpecialChars()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2968
            $splittedContent = array_reverse(/** @scrutinizer ignore-type */ $splittedContent);
Loading history...
2969
        }
2970
        // Crop the text (chars of tag-blocks are not counted).
2971
        $strLen = 0;
2972
        // This is the offset of the content item which was cropped.
2973
        $croppedOffset = null;
2974
        $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

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

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

4241
                        $numericKey = $this->getKey(/** @scrutinizer ignore-type */ $keyParts[0], $tsfe->tmpl->rootLine);
Loading history...
4242
                        $retVal = $this->rootLineValue($numericKey, 'title', strtolower($keyParts[1] ?? '') === 'slide');
4243
                        break;
4244
                    case 'levelmedia':
4245
                        $keyParts = GeneralUtility::trimExplode(',', $key);
4246
                        $numericKey = $this->getKey($keyParts[0], $tsfe->tmpl->rootLine);
4247
                        $retVal = $this->rootLineValue($numericKey, 'media', strtolower($keyParts[1] ?? '') === 'slide');
4248
                        break;
4249
                    case 'leveluid':
4250
                        $numericKey = $this->getKey($key, $tsfe->tmpl->rootLine);
4251
                        $retVal = $this->rootLineValue($numericKey, 'uid');
4252
                        break;
4253
                    case 'levelfield':
4254
                        $keyParts = GeneralUtility::trimExplode(',', $key);
4255
                        $numericKey = $this->getKey($keyParts[0], $tsfe->tmpl->rootLine);
4256
                        $retVal = $this->rootLineValue($numericKey, $keyParts[1], strtolower($keyParts[2] ?? '') === 'slide');
4257
                        break;
4258
                    case 'fullrootline':
4259
                        $keyParts = GeneralUtility::trimExplode(',', $key);
4260
                        $fullKey = (int)$keyParts[0] - count($tsfe->tmpl->rootLine) + count($tsfe->rootLine);
4261
                        if ($fullKey >= 0) {
4262
                            $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

4262
                            $retVal = $this->rootLineValue($fullKey, $keyParts[1], /** @scrutinizer ignore-type */ stristr($keyParts[2] ?? '', 'slide'), $tsfe->rootLine);
Loading history...
4263
                        }
4264
                        break;
4265
                    case 'date':
4266
                        if (!$key) {
4267
                            $key = 'd/m Y';
4268
                        }
4269
                        $retVal = date($key, $GLOBALS['EXEC_TIME']);
4270
                        break;
4271
                    case 'page':
4272
                        $retVal = $tsfe->page[$key];
4273
                        break;
4274
                    case 'pagelayout':
4275
                        $retVal = GeneralUtility::makeInstance(PageLayoutResolver::class)
4276
                            ->getLayoutForPage($tsfe->page, $tsfe->rootLine);
4277
                        break;
4278
                    case 'current':
4279
                        $retVal = $this->data[$this->currentValKey] ?? null;
4280
                        break;
4281
                    case 'db':
4282
                        $selectParts = GeneralUtility::trimExplode(':', $key);
4283
                        $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

4283
                        $db_rec = $tsfe->sys_page->getRawRecord($selectParts[0], /** @scrutinizer ignore-type */ $selectParts[1]);
Loading history...
4284
                        if (is_array($db_rec) && $selectParts[2]) {
4285
                            $retVal = $db_rec[$selectParts[2]];
4286
                        }
4287
                        break;
4288
                    case 'lll':
4289
                        $retVal = $tsfe->sL('LLL:' . $key);
4290
                        break;
4291
                    case 'path':
4292
                        try {
4293
                            $retVal = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($key);
4294
                        } catch (Exception $e) {
4295
                            // do nothing in case the file path is invalid
4296
                            $retVal = null;
4297
                        }
4298
                        break;
4299
                    case 'cobj':
4300
                        switch ($key) {
4301
                            case 'parentRecordNumber':
4302
                                $retVal = $this->parentRecordNumber;
4303
                                break;
4304
                        }
4305
                        break;
4306
                    case 'debug':
4307
                        switch ($key) {
4308
                            case 'rootLine':
4309
                                $retVal = DebugUtility::viewArray($tsfe->tmpl->rootLine);
4310
                                break;
4311
                            case 'fullRootLine':
4312
                                $retVal = DebugUtility::viewArray($tsfe->rootLine);
4313
                                break;
4314
                            case 'data':
4315
                                $retVal = DebugUtility::viewArray($this->data);
4316
                                break;
4317
                            case 'register':
4318
                                $retVal = DebugUtility::viewArray($tsfe->register);
4319
                                break;
4320
                            case 'page':
4321
                                $retVal = DebugUtility::viewArray($tsfe->page);
4322
                                break;
4323
                        }
4324
                        break;
4325
                    case 'flexform':
4326
                        $keyParts = GeneralUtility::trimExplode(':', $key, true);
4327
                        if (count($keyParts) === 2 && isset($this->data[$keyParts[0]])) {
4328
                            $flexFormContent = $this->data[$keyParts[0]];
4329
                            if (!empty($flexFormContent)) {
4330
                                $flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
4331
                                $flexFormKey = str_replace('.', '|', $keyParts[1]);
4332
                                $settings = $flexFormService->convertFlexFormContentToArray($flexFormContent);
4333
                                $retVal = $this->getGlobal($flexFormKey, $settings);
4334
                            }
4335
                        }
4336
                        break;
4337
                    case 'session':
4338
                        $keyParts = GeneralUtility::trimExplode('|', $key, true);
4339
                        $sessionKey = array_shift($keyParts);
4340
                        $retVal = $this->getTypoScriptFrontendController()->fe_user->getSessionData($sessionKey);
4341
                        foreach ($keyParts as $keyPart) {
4342
                            if (is_object($retVal)) {
4343
                                $retVal = $retVal->{$keyPart};
4344
                            } elseif (is_array($retVal)) {
4345
                                $retVal = $retVal[$keyPart];
4346
                            } else {
4347
                                $retVal = '';
4348
                                break;
4349
                            }
4350
                        }
4351
                        if (!is_scalar($retVal)) {
4352
                            $retVal = '';
4353
                        }
4354
                        break;
4355
                    case 'context':
4356
                        $context = GeneralUtility::makeInstance(Context::class);
4357
                        [$aspectName, $propertyName] = GeneralUtility::trimExplode(':', $key, true, 2);
4358
                        $retVal = $context->getPropertyFromAspect($aspectName, $propertyName, '');
4359
                        if (is_array($retVal)) {
4360
                            $retVal = implode(',', $retVal);
4361
                        }
4362
                        if (!is_scalar($retVal)) {
4363
                            $retVal = '';
4364
                        }
4365
                        break;
4366
                    case 'site':
4367
                        $site = $this->getTypoScriptFrontendController()->getSite();
4368
                        if ($key === 'identifier') {
4369
                            $retVal = $site->getIdentifier();
4370
                        } elseif ($key === 'base') {
4371
                            $retVal = $site->getBase();
4372
                        } else {
4373
                            try {
4374
                                $retVal = ArrayUtility::getValueByPath($site->getConfiguration(), $key, '.');
4375
                            } catch (MissingArrayPathException $exception) {
4376
                                $this->logger->warning(sprintf('getData() with "%s" failed', $key), ['exception' => $exception]);
4377
                            }
4378
                        }
4379
                        break;
4380
                    case 'sitelanguage':
4381
                        $siteLanguage = $this->getTypoScriptFrontendController()->getLanguage();
4382
                        $config = $siteLanguage->toArray();
4383
                        if (isset($config[$key])) {
4384
                            $retVal = $config[$key];
4385
                        }
4386
                        break;
4387
                }
4388
            }
4389
4390
            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getData'] ?? [] as $className) {
4391
                $hookObject = GeneralUtility::makeInstance($className);
4392
                if (!$hookObject instanceof ContentObjectGetDataHookInterface) {
4393
                    throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetDataHookInterface::class, 1195044480);
4394
                }
4395
                $ref = $this; // introduced for phpstan to not lose type information when passing $this into callUserFunction
4396
                $retVal = $hookObject->getDataExtension($string, $fieldArray, $secVal, $retVal, $ref);
4397
            }
4398
        }
4399
        return $retVal;
4400
    }
4401
4402
    /**
4403
     * Gets file information. This is a helper function for the getData() method above, which resolves e.g.
4404
     * page.10.data = file:current:title
4405
     * or
4406
     * page.10.data = file:17:title
4407
     *
4408
     * @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.)
4409
     * @return string|int The value as retrieved from the file object.
4410
     */
4411
    protected function getFileDataKey($key)
4412
    {
4413
        [$fileUidOrCurrentKeyword, $requestedFileInformationKey] = explode(':', $key, 3);
4414
        try {
4415
            if ($fileUidOrCurrentKeyword === 'current') {
4416
                $fileObject = $this->getCurrentFile();
4417
            } elseif (MathUtility::canBeInterpretedAsInteger($fileUidOrCurrentKeyword)) {
4418
                /** @var ResourceFactory $fileFactory */
4419
                $fileFactory = GeneralUtility::makeInstance(ResourceFactory::class);
4420
                $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

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

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

5049
                $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...
5050
            }
5051
        } else {
5052
            // like str_rot13() but with a variable offset and a wider character range
5053
            $len = strlen($string);
5054
            $offset = (int)$type;
5055
            for ($i = 0; $i < $len; $i++) {
5056
                $charValue = ord($string[$i]);
5057
                // 0-9 . , - + / :
5058
                if ($charValue >= 43 && $charValue <= 58) {
5059
                    $out .= $this->encryptCharcode($charValue, 43, 58, $offset);
5060
                } elseif ($charValue >= 64 && $charValue <= 90) {
5061
                    // A-Z @
5062
                    $out .= $this->encryptCharcode($charValue, 64, 90, $offset);
5063
                } elseif ($charValue >= 97 && $charValue <= 122) {
5064
                    // a-z
5065
                    $out .= $this->encryptCharcode($charValue, 97, 122, $offset);
5066
                } else {
5067
                    $out .= $string[$i];
5068
                }
5069
            }
5070
        }
5071
        return $out;
5072
    }
5073
5074
    /**
5075
     * Encryption (or decryption) of a single character.
5076
     * Within the given range the character is shifted with the supplied offset.
5077
     *
5078
     * @param int $n Ordinal of input character
5079
     * @param int $start Start of range
5080
     * @param int $end End of range
5081
     * @param int $offset Offset
5082
     * @return string encoded/decoded version of character
5083
     */
5084
    protected function encryptCharcode($n, $start, $end, $offset)
5085
    {
5086
        $n = $n + $offset;
5087
        if ($offset > 0 && $n > $end) {
5088
            $n = $start + ($n - $end - 1);
5089
        } elseif ($offset < 0 && $n < $start) {
5090
            $n = $end - ($start - $n - 1);
5091
        }
5092
        return chr($n);
5093
    }
5094
5095
    /**
5096
     * Gets the query arguments and assembles them for URLs.
5097
     * Arguments may be removed or set, depending on configuration.
5098
     *
5099
     * @param array $conf Configuration
5100
     * @param array $overruleQueryArguments Multidimensional key/value pairs that overrule incoming query arguments
5101
     * @param bool $forceOverruleArguments If set, key/value pairs not in the query but the overrule array will be set
5102
     * @return string The URL query part (starting with a &)
5103
     */
5104
    public function getQueryArguments($conf, $overruleQueryArguments = [], $forceOverruleArguments = false)
5105
    {
5106
        $exclude = [];
5107
        $method = (string)($conf['method'] ?? '');
5108
        if ($method === 'GET') {
5109
            $currentQueryArray = GeneralUtility::_GET();
5110
        } else {
5111
            $currentQueryArray = [];
5112
            parse_str($this->getEnvironmentVariable('QUERY_STRING'), $currentQueryArray);
5113
        }
5114
        if ($conf['exclude'] ?? false) {
5115
            $excludeString = str_replace(',', '&', $conf['exclude']);
5116
            $excludedQueryParts = [];
5117
            parse_str($excludeString, $excludedQueryParts);
5118
            // never repeat id
5119
            $exclude['id'] = 0;
5120
            $newQueryArray = ArrayUtility::arrayDiffAssocRecursive($currentQueryArray, $excludedQueryParts);
5121
        } else {
5122
            $newQueryArray = $currentQueryArray;
5123
        }
5124
        ArrayUtility::mergeRecursiveWithOverrule($newQueryArray, $overruleQueryArguments, $forceOverruleArguments);
5125
        return HttpUtility::buildQueryString($newQueryArray, '&');
5126
    }
5127
5128
    /***********************************************
5129
     *
5130
     * Miscellaneous functions, stand alone
5131
     *
5132
     ***********************************************/
5133
    /**
5134
     * Wrapping a string.
5135
     * Implements the TypoScript "wrap" property.
5136
     * Example: $content = "HELLO WORLD" and $wrap = "<strong> | </strong>", result: "<strong>HELLO WORLD</strong>"
5137
     *
5138
     * @param string $content The content to wrap
5139
     * @param string $wrap The wrap value, eg. "<strong> | </strong>
5140
     * @param string $char The char used to split the wrapping value, default is "|
5141
     * @return string Wrapped input string
5142
     * @see noTrimWrap()
5143
     */
5144
    public function wrap($content, $wrap, $char = '|')
5145
    {
5146
        if ($wrap) {
5147
            $wrapArr = explode($char, $wrap);
5148
            $content = trim($wrapArr[0] ?? '') . $content . trim($wrapArr[1] ?? '');
5149
        }
5150
        return $content;
5151
    }
5152
5153
    /**
5154
     * Wrapping a string, preserving whitespace in wrap value.
5155
     * Notice that the wrap value uses part 1/2 to wrap (and not 0/1 which wrap() does)
5156
     *
5157
     * @param string $content The content to wrap, eg. "HELLO WORLD
5158
     * @param string $wrap The wrap value, eg. " | <strong> | </strong>
5159
     * @param string $char The char used to split the wrapping value, default is "|"
5160
     * @return string Wrapped input string, eg. " <strong> HELLO WORD </strong>
5161
     * @see wrap()
5162
     */
5163
    public function noTrimWrap($content, $wrap, $char = '|')
5164
    {
5165
        if ($wrap) {
5166
            // expects to be wrapped with (at least) 3 characters (before, middle, after)
5167
            // anything else is not taken into account
5168
            $wrapArr = explode($char, $wrap, 4);
5169
            $content = $wrapArr[1] . $content . $wrapArr[2];
5170
        }
5171
        return $content;
5172
    }
5173
5174
    /**
5175
     * Calling a user function/class-method
5176
     * Notice: For classes the instantiated object will have the internal variable, $cObj, set to be a *reference* to $this (the parent/calling object).
5177
     *
5178
     * @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.
5179
     * @param array $conf The TypoScript configuration to pass the function
5180
     * @param string $content The content string to pass the function
5181
     * @return string The return content from the function call. Should probably be a string.
5182
     * @see stdWrap()
5183
     * @see typoLink()
5184
     * @see _parseFunc()
5185
     */
5186
    public function callUserFunction($funcName, $conf, $content)
5187
    {
5188
        // Split parts
5189
        $parts = explode('->', $funcName);
5190
        if (count($parts) === 2) {
5191
            // Check whether PHP class is available
5192
            if (class_exists($parts[0])) {
5193
                if ($this->container && $this->container->has($parts[0])) {
5194
                    $classObj = $this->container->get($parts[0]);
5195
                } else {
5196
                    $classObj = GeneralUtility::makeInstance($parts[0]);
5197
                }
5198
                if (is_object($classObj) && method_exists($classObj, $parts[1])) {
5199
                    $classObj->cObj = $this;
5200
                    $content = call_user_func_array([
5201
                        $classObj,
5202
                        $parts[1]
5203
                    ], [
5204
                        $content,
5205
                        $conf
5206
                    ]);
5207
                } else {
5208
                    $this->getTimeTracker()->setTSlogMessage('Method "' . $parts[1] . '" did not exist in class "' . $parts[0] . '"', 3);
5209
                }
5210
            } else {
5211
                $this->getTimeTracker()->setTSlogMessage('Class "' . $parts[0] . '" did not exist', 3);
5212
            }
5213
        } elseif (function_exists($funcName)) {
5214
            $content = call_user_func($funcName, $content, $conf);
5215
        } else {
5216
            $this->getTimeTracker()->setTSlogMessage('Function "' . $funcName . '" did not exist', 3);
5217
        }
5218
        return $content;
5219
    }
5220
5221
    /**
5222
     * Cleans up a string of keywords. Keywords at splitted by "," (comma)  ";" (semi colon) and linebreak
5223
     *
5224
     * @param string $content String of keywords
5225
     * @return string Cleaned up string, keywords will be separated by a comma only.
5226
     */
5227
    public function keywords($content)
5228
    {
5229
        $listArr = preg_split('/[,;' . LF . ']/', $content);
5230
        foreach ($listArr as $k => $v) {
5231
            $listArr[$k] = trim($v);
5232
        }
5233
        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

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

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

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

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