[BUGFIX] Include DB queries with free mode records
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Classes / ContentObject / ContentObjectRenderer.php
1 <?php
2 namespace TYPO3\CMS\Frontend\ContentObject;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use Doctrine\DBAL\DBALException;
18 use Doctrine\DBAL\Driver\Statement;
19 use Psr\Log\LoggerAwareInterface;
20 use Psr\Log\LoggerAwareTrait;
21 use TYPO3\CMS\Core\Cache\CacheManager;
22 use TYPO3\CMS\Core\Context\Context;
23 use TYPO3\CMS\Core\Context\LanguageAspect;
24 use TYPO3\CMS\Core\Core\Environment;
25 use TYPO3\CMS\Core\Database\Connection;
26 use TYPO3\CMS\Core\Database\ConnectionPool;
27 use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
28 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
29 use TYPO3\CMS\Core\Database\Query\QueryHelper;
30 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
31 use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
32 use TYPO3\CMS\Core\Html\HtmlParser;
33 use TYPO3\CMS\Core\Imaging\ImageManipulation\Area;
34 use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
35 use TYPO3\CMS\Core\LinkHandling\LinkService;
36 use TYPO3\CMS\Core\Log\LogManager;
37 use TYPO3\CMS\Core\Mail\MailMessage;
38 use TYPO3\CMS\Core\Resource\Exception;
39 use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
40 use TYPO3\CMS\Core\Resource\File;
41 use TYPO3\CMS\Core\Resource\FileInterface;
42 use TYPO3\CMS\Core\Resource\FileReference;
43 use TYPO3\CMS\Core\Resource\Folder;
44 use TYPO3\CMS\Core\Resource\ProcessedFile;
45 use TYPO3\CMS\Core\Resource\ResourceFactory;
46 use TYPO3\CMS\Core\Resource\StorageRepository;
47 use TYPO3\CMS\Core\Service\DependencyOrderingService;
48 use TYPO3\CMS\Core\Service\FlexFormService;
49 use TYPO3\CMS\Core\Service\MarkerBasedTemplateService;
50 use TYPO3\CMS\Core\Site\Entity\Site;
51 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
52 use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
53 use TYPO3\CMS\Core\TypoScript\TypoScriptService;
54 use TYPO3\CMS\Core\Utility\ArrayUtility;
55 use TYPO3\CMS\Core\Utility\DebugUtility;
56 use TYPO3\CMS\Core\Utility\GeneralUtility;
57 use TYPO3\CMS\Core\Utility\HttpUtility;
58 use TYPO3\CMS\Core\Utility\MailUtility;
59 use TYPO3\CMS\Core\Utility\MathUtility;
60 use TYPO3\CMS\Core\Utility\PathUtility;
61 use TYPO3\CMS\Core\Utility\StringUtility;
62 use TYPO3\CMS\Core\Versioning\VersionState;
63 use TYPO3\CMS\Frontend\ContentObject\Exception\ContentRenderingException;
64 use TYPO3\CMS\Frontend\ContentObject\Exception\ExceptionHandlerInterface;
65 use TYPO3\CMS\Frontend\ContentObject\Exception\ProductionExceptionHandler;
66 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
67 use TYPO3\CMS\Frontend\Http\UrlProcessorInterface;
68 use TYPO3\CMS\Frontend\Imaging\GifBuilder;
69 use TYPO3\CMS\Frontend\Page\PageRepository;
70 use TYPO3\CMS\Frontend\Resource\FilePathSanitizer;
71 use TYPO3\CMS\Frontend\Service\TypoLinkCodecService;
72 use TYPO3\CMS\Frontend\Typolink\AbstractTypolinkBuilder;
73 use TYPO3\CMS\Frontend\Typolink\UnableToLinkException;
74
75 /**
76 * This class contains all main TypoScript features.
77 * This includes the rendering of TypoScript content objects (cObjects).
78 * Is the backbone of TypoScript Template rendering.
79 *
80 * There are lots of functions you can use from your include-scripts.
81 * The class is normally instantiated and referred to as "cObj".
82 * 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.
83 */
84 class ContentObjectRenderer implements LoggerAwareInterface
85 {
86 use LoggerAwareTrait;
87
88 /**
89 * @var array
90 */
91 public $align = [
92 'center',
93 'right',
94 'left'
95 ];
96
97 /**
98 * stdWrap functions in their correct order
99 *
100 * @see stdWrap()
101 */
102 public $stdWrapOrder = [
103 'stdWrapPreProcess' => 'hook',
104 // this is a placeholder for the first Hook
105 'cacheRead' => 'hook',
106 // this is a placeholder for checking if the content is available in cache
107 'setContentToCurrent' => 'boolean',
108 'setContentToCurrent.' => 'array',
109 'addPageCacheTags' => 'string',
110 'addPageCacheTags.' => 'array',
111 'setCurrent' => 'string',
112 'setCurrent.' => 'array',
113 'lang.' => 'array',
114 'data' => 'getText',
115 'data.' => 'array',
116 'field' => 'fieldName',
117 'field.' => 'array',
118 'current' => 'boolean',
119 'current.' => 'array',
120 'cObject' => 'cObject',
121 'cObject.' => 'array',
122 'numRows.' => 'array',
123 // @deprecated - will be removed in TYPO3 v10.0.
124 'filelist' => 'dir',
125 // @deprecated - will be removed in TYPO3 v10.0.
126 'filelist.' => 'array',
127 'preUserFunc' => 'functionName',
128 'stdWrapOverride' => 'hook',
129 // this is a placeholder for the second Hook
130 'override' => 'string',
131 'override.' => 'array',
132 'preIfEmptyListNum' => 'listNum',
133 'preIfEmptyListNum.' => 'array',
134 'ifNull' => 'string',
135 'ifNull.' => 'array',
136 'ifEmpty' => 'string',
137 'ifEmpty.' => 'array',
138 'ifBlank' => 'string',
139 'ifBlank.' => 'array',
140 'listNum' => 'listNum',
141 'listNum.' => 'array',
142 'trim' => 'boolean',
143 'trim.' => 'array',
144 'strPad.' => 'array',
145 'stdWrap' => 'stdWrap',
146 'stdWrap.' => 'array',
147 'stdWrapProcess' => 'hook',
148 // this is a placeholder for the third Hook
149 'required' => 'boolean',
150 'required.' => 'array',
151 'if.' => 'array',
152 'fieldRequired' => 'fieldName',
153 'fieldRequired.' => 'array',
154 'csConv' => 'string',
155 'csConv.' => 'array',
156 'parseFunc' => 'objectpath',
157 'parseFunc.' => 'array',
158 'HTMLparser' => 'boolean',
159 'HTMLparser.' => 'array',
160 'split.' => 'array',
161 'replacement.' => 'array',
162 'prioriCalc' => 'boolean',
163 'prioriCalc.' => 'array',
164 'char' => 'integer',
165 'char.' => 'array',
166 'intval' => 'boolean',
167 'intval.' => 'array',
168 'hash' => 'string',
169 'hash.' => 'array',
170 'round' => 'boolean',
171 'round.' => 'array',
172 'numberFormat.' => 'array',
173 'expandList' => 'boolean',
174 'expandList.' => 'array',
175 'date' => 'dateconf',
176 'date.' => 'array',
177 'strtotime' => 'strtotimeconf',
178 'strtotime.' => 'array',
179 'strftime' => 'strftimeconf',
180 'strftime.' => 'array',
181 'age' => 'boolean',
182 'age.' => 'array',
183 'case' => 'case',
184 'case.' => 'array',
185 'bytes' => 'boolean',
186 'bytes.' => 'array',
187 'substring' => 'parameters',
188 'substring.' => 'array',
189 'cropHTML' => 'crop',
190 'cropHTML.' => 'array',
191 'stripHtml' => 'boolean',
192 'stripHtml.' => 'array',
193 'crop' => 'crop',
194 'crop.' => 'array',
195 'rawUrlEncode' => 'boolean',
196 'rawUrlEncode.' => 'array',
197 'htmlSpecialChars' => 'boolean',
198 'htmlSpecialChars.' => 'array',
199 'encodeForJavaScriptValue' => 'boolean',
200 'encodeForJavaScriptValue.' => 'array',
201 'doubleBrTag' => 'string',
202 'doubleBrTag.' => 'array',
203 'br' => 'boolean',
204 'br.' => 'array',
205 'brTag' => 'string',
206 'brTag.' => 'array',
207 'encapsLines.' => 'array',
208 'keywords' => 'boolean',
209 'keywords.' => 'array',
210 'innerWrap' => 'wrap',
211 'innerWrap.' => 'array',
212 'innerWrap2' => 'wrap',
213 'innerWrap2.' => 'array',
214 // @deprecated - will be removed in TYPO3 v10.0.
215 'addParams.' => 'array',
216 // @deprecated - will be removed in TYPO3 v10.0.
217 'filelink.' => '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 * This is used by checkPid, that checks if pages are accessible. The $checkPid_cache['page_uid'] is set TRUE or FALSE upon this check featuring a caching function for the next request.
352 *
353 * @var array
354 */
355 public $checkPid_cache = [];
356
357 /**
358 * @var string
359 */
360 public $checkPid_badDoktypeList = '255';
361
362 /**
363 * This will be set by typoLink() to the url of the most recent link created.
364 *
365 * @var string
366 */
367 public $lastTypoLinkUrl = '';
368
369 /**
370 * DO. link target.
371 *
372 * @var string
373 */
374 public $lastTypoLinkTarget = '';
375
376 /**
377 * @var array
378 */
379 public $lastTypoLinkLD = [];
380
381 /**
382 * array that registers rendered content elements (or any table) to make sure they are not rendered recursively!
383 *
384 * @var array
385 */
386 public $recordRegister = [];
387
388 /**
389 * Additionally registered content object types and class names
390 *
391 * @var array
392 */
393 protected $cObjHookObjectsRegistry = [];
394
395 /**
396 * @var array
397 */
398 public $cObjHookObjectsArr = [];
399
400 /**
401 * Containing hook objects for stdWrap
402 *
403 * @var array
404 */
405 protected $stdWrapHookObjects = [];
406
407 /**
408 * Containing hook objects for getImgResource
409 *
410 * @var array
411 */
412 protected $getImgResourceHookObjects;
413
414 /**
415 * @var File Current file objects (during iterations over files)
416 */
417 protected $currentFile;
418
419 /**
420 * Set to TRUE by doConvertToUserIntObject() if USER object wants to become USER_INT
421 */
422 public $doConvertToUserIntObject = false;
423
424 /**
425 * Indicates current object type. Can hold one of OBJECTTYPE_ constants or FALSE.
426 * The value is set and reset inside USER() function. Any time outside of
427 * USER() it is FALSE.
428 */
429 protected $userObjectType = false;
430
431 /**
432 * @var array
433 */
434 protected $stopRendering = [];
435
436 /**
437 * @var int
438 */
439 protected $stdWrapRecursionLevel = 0;
440
441 /**
442 * @var TypoScriptFrontendController
443 */
444 protected $typoScriptFrontendController;
445
446 /**
447 * Indicates that object type is USER.
448 *
449 * @see ContentObjectRender::$userObjectType
450 */
451 const OBJECTTYPE_USER_INT = 1;
452 /**
453 * Indicates that object type is USER.
454 *
455 * @see ContentObjectRender::$userObjectType
456 */
457 const OBJECTTYPE_USER = 2;
458
459 /**
460 * @param TypoScriptFrontendController $typoScriptFrontendController
461 */
462 public function __construct(TypoScriptFrontendController $typoScriptFrontendController = null)
463 {
464 $this->typoScriptFrontendController = $typoScriptFrontendController;
465 $this->contentObjectClassMap = $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'];
466 }
467
468 /**
469 * Prevent several objects from being serialized.
470 * If currentFile is set, it is either a File or a FileReference object. As the object itself can't be serialized,
471 * we have store a hash and restore the object in __wakeup()
472 *
473 * @return array
474 */
475 public function __sleep()
476 {
477 $vars = get_object_vars($this);
478 unset($vars['typoScriptFrontendController'], $vars['logger']);
479 if ($this->currentFile instanceof FileReference) {
480 $this->currentFile = 'FileReference:' . $this->currentFile->getUid();
481 } elseif ($this->currentFile instanceof File) {
482 $this->currentFile = 'File:' . $this->currentFile->getIdentifier();
483 } else {
484 unset($vars['currentFile']);
485 }
486 return array_keys($vars);
487 }
488
489 /**
490 * Restore currentFile from hash.
491 * If currentFile references a File, the identifier equals file identifier.
492 * If it references a FileReference the identifier equals the uid of the reference.
493 */
494 public function __wakeup()
495 {
496 if (isset($GLOBALS['TSFE'])) {
497 $this->typoScriptFrontendController = $GLOBALS['TSFE'];
498 }
499 if ($this->currentFile !== null && is_string($this->currentFile)) {
500 list($objectType, $identifier) = explode(':', $this->currentFile, 2);
501 try {
502 if ($objectType === 'File') {
503 $this->currentFile = ResourceFactory::getInstance()->retrieveFileOrFolderObject($identifier);
504 } elseif ($objectType === 'FileReference') {
505 $this->currentFile = ResourceFactory::getInstance()->getFileReferenceObject($identifier);
506 }
507 } catch (ResourceDoesNotExistException $e) {
508 $this->currentFile = null;
509 }
510 }
511 $this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
512 }
513
514 /**
515 * Allow injecting content object class map.
516 *
517 * This method is private API, please use configuration
518 * $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'] to add new content objects
519 *
520 * @internal
521 * @param array $contentObjectClassMap
522 */
523 public function setContentObjectClassMap(array $contentObjectClassMap)
524 {
525 $this->contentObjectClassMap = $contentObjectClassMap;
526 }
527
528 /**
529 * Register a single content object name to class name
530 *
531 * This method is private API, please use configuration
532 * $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'] to add new content objects
533 *
534 * @param string $className
535 * @param string $contentObjectName
536 * @internal
537 */
538 public function registerContentObjectClass($className, $contentObjectName)
539 {
540 $this->contentObjectClassMap[$contentObjectName] = $className;
541 }
542
543 /**
544 * Class constructor.
545 * Well, it has to be called manually since it is not a real constructor function.
546 * 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.
547 *
548 * @param array $data The record data that is rendered.
549 * @param string $table The table that the data record is from.
550 */
551 public function start($data, $table = '')
552 {
553 $this->data = $data;
554 $this->table = $table;
555 $this->currentRecord = $table !== ''
556 ? $table . ':' . ($this->data['uid'] ?? '')
557 : '';
558 $this->parameters = [];
559 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['cObjTypeAndClass'] ?? [] as $classArr) {
560 $this->cObjHookObjectsRegistry[$classArr[0]] = $classArr[1];
561 }
562 $this->stdWrapHookObjects = [];
563 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap'] ?? [] as $className) {
564 $hookObject = GeneralUtility::makeInstance($className);
565 if (!$hookObject instanceof ContentObjectStdWrapHookInterface) {
566 throw new \UnexpectedValueException($className . ' must implement interface ' . ContentObjectStdWrapHookInterface::class, 1195043965);
567 }
568 $this->stdWrapHookObjects[] = $hookObject;
569 }
570 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['postInit'] ?? [] as $className) {
571 $postInitializationProcessor = GeneralUtility::makeInstance($className);
572 if (!$postInitializationProcessor instanceof ContentObjectPostInitHookInterface) {
573 throw new \UnexpectedValueException($className . ' must implement interface ' . ContentObjectPostInitHookInterface::class, 1274563549);
574 }
575 $postInitializationProcessor->postProcessContentObjectInitialization($this);
576 }
577 }
578
579 /**
580 * Returns the current table
581 *
582 * @return string
583 */
584 public function getCurrentTable()
585 {
586 return $this->table;
587 }
588
589 /**
590 * Gets the 'getImgResource' hook objects.
591 * The first call initializes the accordant objects.
592 *
593 * @return array The 'getImgResource' hook objects (if any)
594 */
595 protected function getGetImgResourceHookObjects()
596 {
597 if (!isset($this->getImgResourceHookObjects)) {
598 $this->getImgResourceHookObjects = [];
599 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImgResource'] ?? [] as $className) {
600 $hookObject = GeneralUtility::makeInstance($className);
601 if (!$hookObject instanceof ContentObjectGetImageResourceHookInterface) {
602 throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetImageResourceHookInterface::class, 1218636383);
603 }
604 $this->getImgResourceHookObjects[] = $hookObject;
605 }
606 }
607 return $this->getImgResourceHookObjects;
608 }
609
610 /**
611 * Sets the internal variable parentRecord with information about current record.
612 * 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.
613 *
614 * @param array $data The record array
615 * @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.
616 * @internal
617 */
618 public function setParent($data, $currentRecord)
619 {
620 $this->parentRecord = [
621 'data' => $data,
622 'currentRecord' => $currentRecord
623 ];
624 }
625
626 /***********************************************
627 *
628 * CONTENT_OBJ:
629 *
630 ***********************************************/
631 /**
632 * Returns the "current" value.
633 * 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.
634 * It's like "load accumulator" in the good old C64 days... basically a "register" you can use as you like.
635 * 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.
636 *
637 * @return mixed The "current" value
638 */
639 public function getCurrentVal()
640 {
641 return $this->data[$this->currentValKey];
642 }
643
644 /**
645 * Sets the "current" value.
646 *
647 * @param mixed $value The variable that you want to set as "current
648 * @see getCurrentVal()
649 */
650 public function setCurrentVal($value)
651 {
652 $this->data[$this->currentValKey] = $value;
653 }
654
655 /**
656 * Rendering of a "numerical array" of cObjects from TypoScript
657 * Will call ->cObjGetSingle() for each cObject found and accumulate the output.
658 *
659 * @param array $setup array with cObjects as values.
660 * @param string $addKey A prefix for the debugging information
661 * @return string Rendered output from the cObjects in the array.
662 * @see cObjGetSingle()
663 */
664 public function cObjGet($setup, $addKey = '')
665 {
666 if (!is_array($setup)) {
667 return '';
668 }
669 $sKeyArray = ArrayUtility::filterAndSortByNumericKeys($setup);
670 $content = '';
671 foreach ($sKeyArray as $theKey) {
672 $theValue = $setup[$theKey];
673 if ((int)$theKey && strpos($theKey, '.') === false) {
674 $conf = $setup[$theKey . '.'];
675 $content .= $this->cObjGetSingle($theValue, $conf, $addKey . $theKey);
676 }
677 }
678 return $content;
679 }
680
681 /**
682 * Renders a content object
683 *
684 * @param string $name The content object name, eg. "TEXT" or "USER" or "IMAGE
685 * @param array $conf The array with TypoScript properties for the content object
686 * @param string $TSkey A string label used for the internal debugging tracking.
687 * @return string cObject output
688 * @throws \UnexpectedValueException
689 */
690 public function cObjGetSingle($name, $conf, $TSkey = '__')
691 {
692 $content = '';
693 // Checking that the function is not called eternally. This is done by interrupting at a depth of 100
694 $this->getTypoScriptFrontendController()->cObjectDepthCounter--;
695 if ($this->getTypoScriptFrontendController()->cObjectDepthCounter > 0) {
696 $timeTracker = $this->getTimeTracker();
697 $name = trim($name);
698 if ($timeTracker->LR) {
699 $timeTracker->push($TSkey, $name);
700 }
701 // Checking if the COBJ is a reference to another object. (eg. name of 'blabla.blabla = < styles.something')
702 if (isset($name[0]) && $name[0] === '<') {
703 $key = trim(substr($name, 1));
704 $cF = GeneralUtility::makeInstance(TypoScriptParser::class);
705 // $name and $conf is loaded with the referenced values.
706 $confOverride = is_array($conf) ? $conf : [];
707 list($name, $conf) = $cF->getVal($key, $this->getTypoScriptFrontendController()->tmpl->setup);
708 $conf = array_replace_recursive(is_array($conf) ? $conf : [], $confOverride);
709 // Getting the cObject
710 $timeTracker->incStackPointer();
711 $content .= $this->cObjGetSingle($name, $conf, $key);
712 $timeTracker->decStackPointer();
713 } else {
714 $hooked = false;
715 // Application defined cObjects
716 if (!empty($this->cObjHookObjectsRegistry[$name])) {
717 if (empty($this->cObjHookObjectsArr[$name])) {
718 $this->cObjHookObjectsArr[$name] = GeneralUtility::makeInstance($this->cObjHookObjectsRegistry[$name]);
719 }
720 $hookObj = $this->cObjHookObjectsArr[$name];
721 if (method_exists($hookObj, 'cObjGetSingleExt')) {
722 $content .= $hookObj->cObjGetSingleExt($name, $conf, $TSkey, $this);
723 $hooked = true;
724 }
725 }
726 if (!$hooked) {
727 $contentObject = $this->getContentObject($name);
728 if ($contentObject) {
729 $content .= $this->render($contentObject, $conf);
730 } else {
731 // Call hook functions for extra processing
732 if ($name) {
733 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['cObjTypeAndClassDefault'] ?? [] as $className) {
734 $hookObject = GeneralUtility::makeInstance($className);
735 if (!$hookObject instanceof ContentObjectGetSingleHookInterface) {
736 throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetSingleHookInterface::class, 1195043731);
737 }
738 /** @var ContentObjectGetSingleHookInterface $hookObject */
739 $content .= $hookObject->getSingleContentObject($name, (array)$conf, $TSkey, $this);
740 }
741 } else {
742 // Log error in AdminPanel
743 $warning = sprintf('Content Object "%s" does not exist', $name);
744 $timeTracker->setTSlogMessage($warning, 2);
745 }
746 }
747 }
748 }
749 if ($timeTracker->LR) {
750 $timeTracker->pull($content);
751 }
752 }
753 // Increasing on exit...
754 $this->getTypoScriptFrontendController()->cObjectDepthCounter++;
755 return $content;
756 }
757
758 /**
759 * Returns a new content object of type $name.
760 * This content object needs to be registered as content object
761 * in $this->contentObjectClassMap
762 *
763 * @param string $name
764 * @return AbstractContentObject|null
765 * @throws ContentRenderingException
766 */
767 public function getContentObject($name)
768 {
769 if (!isset($this->contentObjectClassMap[$name])) {
770 return null;
771 }
772 $fullyQualifiedClassName = $this->contentObjectClassMap[$name];
773 $contentObject = GeneralUtility::makeInstance($fullyQualifiedClassName, $this);
774 if (!($contentObject instanceof AbstractContentObject)) {
775 throw new ContentRenderingException(sprintf('Registered content object class name "%s" must be an instance of AbstractContentObject, but is not!', $fullyQualifiedClassName), 1422564295);
776 }
777 return $contentObject;
778 }
779
780 /********************************************
781 *
782 * Functions rendering content objects (cObjects)
783 *
784 ********************************************/
785
786 /**
787 * Renders a content object by taking exception and cache handling
788 * into consideration
789 *
790 * @param AbstractContentObject $contentObject Content object instance
791 * @param array $configuration Array of TypoScript properties
792 *
793 * @throws ContentRenderingException
794 * @throws \Exception
795 * @return string
796 */
797 public function render(AbstractContentObject $contentObject, $configuration = [])
798 {
799 $content = '';
800
801 // Evaluate possible cache and return
802 $cacheConfiguration = $configuration['cache.'] ?? null;
803 if ($cacheConfiguration !== null) {
804 unset($configuration['cache.']);
805 $cache = $this->getFromCache($cacheConfiguration);
806 if ($cache !== false) {
807 return $cache;
808 }
809 }
810
811 // Render content
812 try {
813 $content .= $contentObject->render($configuration);
814 } catch (ContentRenderingException $exception) {
815 // Content rendering Exceptions indicate a critical problem which should not be
816 // caught e.g. when something went wrong with Exception handling itself
817 throw $exception;
818 } catch (\Exception $exception) {
819 $exceptionHandler = $this->createExceptionHandler($configuration);
820 if ($exceptionHandler === null) {
821 throw $exception;
822 }
823 $content = $exceptionHandler->handle($exception, $contentObject, $configuration);
824 }
825
826 // Store cache
827 if ($cacheConfiguration !== null) {
828 $key = $this->calculateCacheKey($cacheConfiguration);
829 if (!empty($key)) {
830 /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cacheFrontend */
831 $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash');
832 $tags = $this->calculateCacheTags($cacheConfiguration);
833 $lifetime = $this->calculateCacheLifetime($cacheConfiguration);
834 $cacheFrontend->set($key, $content, $tags, $lifetime);
835 }
836 }
837
838 return $content;
839 }
840
841 /**
842 * Creates the content object exception handler from local content object configuration
843 * or, from global configuration if not explicitly disabled in local configuration
844 *
845 * @param array $configuration
846 * @return ExceptionHandlerInterface|null
847 * @throws ContentRenderingException
848 */
849 protected function createExceptionHandler($configuration = [])
850 {
851 $exceptionHandler = null;
852 $exceptionHandlerClassName = $this->determineExceptionHandlerClassName($configuration);
853 if (!empty($exceptionHandlerClassName)) {
854 $exceptionHandler = GeneralUtility::makeInstance($exceptionHandlerClassName, $this->mergeExceptionHandlerConfiguration($configuration));
855 if (!$exceptionHandler instanceof ExceptionHandlerInterface) {
856 throw new ContentRenderingException('An exception handler was configured but the class does not exist or does not implement the ExceptionHandlerInterface', 1403653369);
857 }
858 }
859
860 return $exceptionHandler;
861 }
862
863 /**
864 * Determine exception handler class name from global and content object configuration
865 *
866 * @param array $configuration
867 * @return string|null
868 */
869 protected function determineExceptionHandlerClassName($configuration)
870 {
871 $exceptionHandlerClassName = null;
872 $tsfe = $this->getTypoScriptFrontendController();
873 if (!isset($tsfe->config['config']['contentObjectExceptionHandler'])) {
874 if (GeneralUtility::getApplicationContext()->isProduction()) {
875 $exceptionHandlerClassName = '1';
876 }
877 } else {
878 $exceptionHandlerClassName = $tsfe->config['config']['contentObjectExceptionHandler'];
879 }
880
881 if (isset($configuration['exceptionHandler'])) {
882 $exceptionHandlerClassName = $configuration['exceptionHandler'];
883 }
884
885 if ($exceptionHandlerClassName === '1') {
886 $exceptionHandlerClassName = ProductionExceptionHandler::class;
887 }
888
889 return $exceptionHandlerClassName;
890 }
891
892 /**
893 * Merges global exception handler configuration with the one from the content object
894 * and returns the merged exception handler configuration
895 *
896 * @param array $configuration
897 * @return array
898 */
899 protected function mergeExceptionHandlerConfiguration($configuration)
900 {
901 $exceptionHandlerConfiguration = [];
902 $tsfe = $this->getTypoScriptFrontendController();
903 if (!empty($tsfe->config['config']['contentObjectExceptionHandler.'])) {
904 $exceptionHandlerConfiguration = $tsfe->config['config']['contentObjectExceptionHandler.'];
905 }
906 if (!empty($configuration['exceptionHandler.'])) {
907 $exceptionHandlerConfiguration = array_replace_recursive($exceptionHandlerConfiguration, $configuration['exceptionHandler.']);
908 }
909
910 return $exceptionHandlerConfiguration;
911 }
912
913 /**
914 * Retrieves a type of object called as USER or USER_INT. Object can detect their
915 * type by using this call. It returns OBJECTTYPE_USER_INT or OBJECTTYPE_USER depending on the
916 * current object execution. In all other cases it will return FALSE to indicate
917 * a call out of context.
918 *
919 * @return mixed One of OBJECTTYPE_ class constants or FALSE
920 */
921 public function getUserObjectType()
922 {
923 return $this->userObjectType;
924 }
925
926 /**
927 * Sets the user object type
928 *
929 * @param mixed $userObjectType
930 */
931 public function setUserObjectType($userObjectType)
932 {
933 $this->userObjectType = $userObjectType;
934 }
935
936 /**
937 * Requests the current USER object to be converted to USER_INT.
938 */
939 public function convertToUserIntObject()
940 {
941 if ($this->userObjectType !== self::OBJECTTYPE_USER) {
942 $this->getTimeTracker()->setTSlogMessage(self::class . '::convertToUserIntObject() is called in the wrong context or for the wrong object type', 2);
943 } else {
944 $this->doConvertToUserIntObject = true;
945 }
946 }
947
948 /************************************
949 *
950 * Various helper functions for content objects:
951 *
952 ************************************/
953 /**
954 * Converts a given config in Flexform to a conf-array
955 *
956 * @param string|array $flexData Flexform data
957 * @param array $conf Array to write the data into, by reference
958 * @param bool $recursive Is set if called recursive. Don't call function with this parameter, it's used inside the function only
959 */
960 public function readFlexformIntoConf($flexData, &$conf, $recursive = false)
961 {
962 if ($recursive === false && is_string($flexData)) {
963 $flexData = GeneralUtility::xml2array($flexData, 'T3');
964 }
965 if (is_array($flexData) && isset($flexData['data']['sDEF']['lDEF'])) {
966 $flexData = $flexData['data']['sDEF']['lDEF'];
967 }
968 if (!is_array($flexData)) {
969 return;
970 }
971 foreach ($flexData as $key => $value) {
972 if (!is_array($value)) {
973 continue;
974 }
975 if (isset($value['el'])) {
976 if (is_array($value['el']) && !empty($value['el'])) {
977 foreach ($value['el'] as $ekey => $element) {
978 if (isset($element['vDEF'])) {
979 $conf[$ekey] = $element['vDEF'];
980 } else {
981 if (is_array($element)) {
982 $this->readFlexformIntoConf($element, $conf[$key][key($element)][$ekey], true);
983 } else {
984 $this->readFlexformIntoConf($element, $conf[$key][$ekey], true);
985 }
986 }
987 }
988 } else {
989 $this->readFlexformIntoConf($value['el'], $conf[$key], true);
990 }
991 }
992 if (isset($value['vDEF'])) {
993 $conf[$key] = $value['vDEF'];
994 }
995 }
996 }
997
998 /**
999 * Returns all parents of the given PID (Page UID) list
1000 *
1001 * @param string $pidList A list of page Content-Element PIDs (Page UIDs) / stdWrap
1002 * @param array $pidConf stdWrap array for the list
1003 * @return string A list of PIDs
1004 * @internal
1005 */
1006 public function getSlidePids($pidList, $pidConf)
1007 {
1008 $pidList = isset($pidConf) ? trim($this->stdWrap($pidList, $pidConf)) : trim($pidList);
1009 if ($pidList === '') {
1010 $pidList = 'this';
1011 }
1012 $tsfe = $this->getTypoScriptFrontendController();
1013 $listArr = null;
1014 if (trim($pidList)) {
1015 $listArr = GeneralUtility::intExplode(',', str_replace('this', $tsfe->contentPid, $pidList));
1016 $listArr = $this->checkPidArray($listArr);
1017 }
1018 $pidList = [];
1019 if (is_array($listArr) && !empty($listArr)) {
1020 foreach ($listArr as $uid) {
1021 $page = $tsfe->sys_page->getPage($uid);
1022 if (!$page['is_siteroot']) {
1023 $pidList[] = $page['pid'];
1024 }
1025 }
1026 }
1027 return implode(',', $pidList);
1028 }
1029
1030 /**
1031 * Returns a <img> tag with the image file defined by $file and processed according to the properties in the TypoScript array.
1032 * Mostly this function is a sub-function to the IMAGE function which renders the IMAGE cObject in TypoScript.
1033 * This function is called by "$this->cImage($conf['file'], $conf);" from IMAGE().
1034 *
1035 * @param string $file File TypoScript resource
1036 * @param array $conf TypoScript configuration properties
1037 * @return string <img> tag, (possibly wrapped in links and other HTML) if any image found.
1038 * @internal
1039 * @see IMAGE()
1040 */
1041 public function cImage($file, $conf)
1042 {
1043 $tsfe = $this->getTypoScriptFrontendController();
1044 $info = $this->getImgResource($file, $conf['file.']);
1045 $tsfe->lastImageInfo = $info;
1046 if (!is_array($info)) {
1047 return '';
1048 }
1049 if (is_file(Environment::getPublicPath() . '/' . $info['3'])) {
1050 $source = $tsfe->absRefPrefix . str_replace('%2F', '/', rawurlencode($info['3']));
1051 } else {
1052 $source = $info[3];
1053 }
1054
1055 $layoutKey = $this->stdWrap($conf['layoutKey'], $conf['layoutKey.']);
1056 $imageTagTemplate = $this->getImageTagTemplate($layoutKey, $conf);
1057 $sourceCollection = $this->getImageSourceCollection($layoutKey, $conf, $file);
1058
1059 // This array is used to collect the image-refs on the page...
1060 $tsfe->imagesOnPage[] = $source;
1061 $altParam = $this->getAltParam($conf);
1062 $params = $this->stdWrapValue('params', $conf);
1063 if ($params !== '' && $params[0] !== ' ') {
1064 $params = ' ' . $params;
1065 }
1066
1067 $imageTagValues = [
1068 'width' => (int)$info[0],
1069 'height' => (int)$info[1],
1070 'src' => htmlspecialchars($source),
1071 'params' => $params,
1072 'altParams' => $altParam,
1073 'border' => $this->getBorderAttr(' border="' . (int)$conf['border'] . '"'),
1074 'sourceCollection' => $sourceCollection,
1075 'selfClosingTagSlash' => !empty($tsfe->xhtmlDoctype) ? ' /' : '',
1076 ];
1077
1078 $markerTemplateEngine = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
1079 $theValue = $markerTemplateEngine->substituteMarkerArray($imageTagTemplate, $imageTagValues, '###|###', true, true);
1080
1081 $linkWrap = isset($conf['linkWrap.']) ? $this->stdWrap($conf['linkWrap'], $conf['linkWrap.']) : $conf['linkWrap'];
1082 if ($linkWrap) {
1083 $theValue = $this->linkWrap($theValue, $linkWrap);
1084 } elseif ($conf['imageLinkWrap']) {
1085 $originalFile = !empty($info['originalFile']) ? $info['originalFile'] : $info['origFile'];
1086 $theValue = $this->imageLinkWrap($theValue, $originalFile, $conf['imageLinkWrap.']);
1087 }
1088 $wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
1089 if ((string)$wrap !== '') {
1090 $theValue = $this->wrap($theValue, $conf['wrap']);
1091 }
1092 return $theValue;
1093 }
1094
1095 /**
1096 * Returns the 'border' attribute for an <img> tag only if the doctype is not xhtml_strict, xhtml_11 or html5
1097 * or if the config parameter 'disableImgBorderAttr' is not set.
1098 *
1099 * @param string $borderAttr The border attribute
1100 * @return string The border attribute
1101 */
1102 public function getBorderAttr($borderAttr)
1103 {
1104 $tsfe = $this->getTypoScriptFrontendController();
1105 $docType = $tsfe->xhtmlDoctype;
1106 if (
1107 $docType !== 'xhtml_strict' && $docType !== 'xhtml_11'
1108 && $tsfe->config['config']['doctype'] !== 'html5'
1109 && !$tsfe->config['config']['disableImgBorderAttr']
1110 ) {
1111 return $borderAttr;
1112 }
1113 return '';
1114 }
1115
1116 /**
1117 * Returns the html-template for rendering the image-Tag if no template is defined via typoscript the
1118 * default <img> tag template is returned
1119 *
1120 * @param string $layoutKey rendering key
1121 * @param array $conf TypoScript configuration properties
1122 * @return string
1123 */
1124 public function getImageTagTemplate($layoutKey, $conf)
1125 {
1126 if ($layoutKey && isset($conf['layout.']) && isset($conf['layout.'][$layoutKey . '.'])) {
1127 $imageTagLayout = $this->stdWrap(
1128 $conf['layout.'][$layoutKey . '.']['element'] ?? '',
1129 $conf['layout.'][$layoutKey . '.']['element.'] ?? []
1130 );
1131 } else {
1132 $imageTagLayout = '<img src="###SRC###" width="###WIDTH###" height="###HEIGHT###" ###PARAMS### ###ALTPARAMS### ###BORDER######SELFCLOSINGTAGSLASH###>';
1133 }
1134 return $imageTagLayout;
1135 }
1136
1137 /**
1138 * Render alternate sources for the image tag. If no source collection is given an empty string is returned.
1139 *
1140 * @param string $layoutKey rendering key
1141 * @param array $conf TypoScript configuration properties
1142 * @param string $file
1143 * @throws \UnexpectedValueException
1144 * @return string
1145 */
1146 public function getImageSourceCollection($layoutKey, $conf, $file)
1147 {
1148 $sourceCollection = '';
1149 if ($layoutKey
1150 && isset($conf['sourceCollection.']) && $conf['sourceCollection.']
1151 && (
1152 isset($conf['layout.'][$layoutKey . '.']['source']) && $conf['layout.'][$layoutKey . '.']['source']
1153 || isset($conf['layout.'][$layoutKey . '.']['source.']) && $conf['layout.'][$layoutKey . '.']['source.']
1154 )
1155 ) {
1156
1157 // find active sourceCollection
1158 $activeSourceCollections = [];
1159 foreach ($conf['sourceCollection.'] as $sourceCollectionKey => $sourceCollectionConfiguration) {
1160 if (substr($sourceCollectionKey, -1) === '.') {
1161 if (empty($sourceCollectionConfiguration['if.']) || $this->checkIf($sourceCollectionConfiguration['if.'])) {
1162 $activeSourceCollections[] = $sourceCollectionConfiguration;
1163 }
1164 }
1165 }
1166
1167 // apply option split to configurations
1168 $tsfe = $this->getTypoScriptFrontendController();
1169 $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
1170 $srcLayoutOptionSplitted = $typoScriptService->explodeConfigurationForOptionSplit((array)$conf['layout.'][$layoutKey . '.'], count($activeSourceCollections));
1171
1172 // render sources
1173 foreach ($activeSourceCollections as $key => $sourceConfiguration) {
1174 $sourceLayout = $this->stdWrap(
1175 $srcLayoutOptionSplitted[$key]['source'] ?? '',
1176 $srcLayoutOptionSplitted[$key]['source.'] ?? []
1177 );
1178
1179 $sourceRenderConfiguration = [
1180 'file' => $file,
1181 'file.' => $conf['file.'] ?? null
1182 ];
1183
1184 if (isset($sourceConfiguration['quality']) || isset($sourceConfiguration['quality.'])) {
1185 $imageQuality = $sourceConfiguration['quality'] ?? '';
1186 if (isset($sourceConfiguration['quality.'])) {
1187 $imageQuality = $this->stdWrap($sourceConfiguration['quality'], $sourceConfiguration['quality.']);
1188 }
1189 if ($imageQuality) {
1190 $sourceRenderConfiguration['file.']['params'] = '-quality ' . (int)$imageQuality;
1191 }
1192 }
1193
1194 if (isset($sourceConfiguration['pixelDensity'])) {
1195 $pixelDensity = (int)$this->stdWrap(
1196 $sourceConfiguration['pixelDensity'] ?? '',
1197 $sourceConfiguration['pixelDensity.'] ?? []
1198 );
1199 } else {
1200 $pixelDensity = 1;
1201 }
1202 $dimensionKeys = ['width', 'height', 'maxW', 'minW', 'maxH', 'minH', 'maxWidth', 'maxHeight', 'XY'];
1203 foreach ($dimensionKeys as $dimensionKey) {
1204 $dimension = $this->stdWrap(
1205 $sourceConfiguration[$dimensionKey] ?? '',
1206 $sourceConfiguration[$dimensionKey . '.'] ?? []
1207 );
1208 if (!$dimension) {
1209 $dimension = $this->stdWrap(
1210 $conf['file.'][$dimensionKey] ?? '',
1211 $conf['file.'][$dimensionKey . '.'] ?? []
1212 );
1213 }
1214 if ($dimension) {
1215 if (strstr($dimension, 'c') !== false && ($dimensionKey === 'width' || $dimensionKey === 'height')) {
1216 $dimensionParts = explode('c', $dimension, 2);
1217 $dimension = ((int)$dimensionParts[0] * $pixelDensity) . 'c';
1218 if ($dimensionParts[1]) {
1219 $dimension .= $dimensionParts[1];
1220 }
1221 } elseif ($dimensionKey === 'XY') {
1222 $dimensionParts = GeneralUtility::intExplode(',', $dimension, false, 2);
1223 $dimension = $dimensionParts[0] * $pixelDensity;
1224 if ($dimensionParts[1]) {
1225 $dimension .= ',' . $dimensionParts[1] * $pixelDensity;
1226 }
1227 } else {
1228 $dimension = (int)$dimension * $pixelDensity;
1229 }
1230 $sourceRenderConfiguration['file.'][$dimensionKey] = $dimension;
1231 // Remove the stdWrap properties for dimension as they have been processed already above.
1232 unset($sourceRenderConfiguration['file.'][$dimensionKey . '.']);
1233 }
1234 }
1235 $sourceInfo = $this->getImgResource($sourceRenderConfiguration['file'], $sourceRenderConfiguration['file.']);
1236 if ($sourceInfo) {
1237 $sourceConfiguration['width'] = $sourceInfo[0];
1238 $sourceConfiguration['height'] = $sourceInfo[1];
1239 $urlPrefix = '';
1240 if (parse_url($sourceInfo[3], PHP_URL_HOST) === null) {
1241 $urlPrefix = $tsfe->absRefPrefix;
1242 }
1243 $sourceConfiguration['src'] = htmlspecialchars($urlPrefix . $sourceInfo[3]);
1244 $sourceConfiguration['selfClosingTagSlash'] = !empty($tsfe->xhtmlDoctype) ? ' /' : '';
1245
1246 $markerTemplateEngine = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
1247 $oneSourceCollection = $markerTemplateEngine->substituteMarkerArray($sourceLayout, $sourceConfiguration, '###|###', true, true);
1248
1249 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImageSourceCollection'] ?? [] as $className) {
1250 $hookObject = GeneralUtility::makeInstance($className);
1251 if (!$hookObject instanceof ContentObjectOneSourceCollectionHookInterface) {
1252 throw new \UnexpectedValueException(
1253 '$hookObject must implement interface ' . ContentObjectOneSourceCollectionHookInterface::class,
1254 1380007853
1255 );
1256 }
1257 $oneSourceCollection = $hookObject->getOneSourceCollection((array)$sourceRenderConfiguration, (array)$sourceConfiguration, $oneSourceCollection, $this);
1258 }
1259
1260 $sourceCollection .= $oneSourceCollection;
1261 }
1262 }
1263 }
1264 return $sourceCollection;
1265 }
1266
1267 /**
1268 * Wraps the input string in link-tags that opens the image in a new window.
1269 *
1270 * @param string $string String to wrap, probably an <img> tag
1271 * @param string|File|FileReference $imageFile The original image file
1272 * @param array $conf TypoScript properties for the "imageLinkWrap" function
1273 * @return string The input string, $string, wrapped as configured.
1274 * @see cImage()
1275 */
1276 public function imageLinkWrap($string, $imageFile, $conf)
1277 {
1278 $string = (string)$string;
1279 $enable = isset($conf['enable.']) ? $this->stdWrap($conf['enable'], $conf['enable.']) : $conf['enable'];
1280 if (!$enable) {
1281 return $string;
1282 }
1283 $content = (string)$this->typoLink($string, $conf['typolink.']);
1284 if (isset($conf['file.'])) {
1285 $imageFile = $this->stdWrap($imageFile, $conf['file.']);
1286 }
1287
1288 if ($imageFile instanceof File) {
1289 $file = $imageFile;
1290 } elseif ($imageFile instanceof FileReference) {
1291 $file = $imageFile->getOriginalFile();
1292 } else {
1293 if (MathUtility::canBeInterpretedAsInteger($imageFile)) {
1294 $file = ResourceFactory::getInstance()->getFileObject((int)$imageFile);
1295 } else {
1296 $file = ResourceFactory::getInstance()->getFileObjectFromCombinedIdentifier($imageFile);
1297 }
1298 }
1299
1300 // Create imageFileLink if not created with typolink
1301 if ($content === $string) {
1302 $parameterNames = ['width', 'height', 'effects', 'bodyTag', 'title', 'wrap', 'crop'];
1303 $parameters = [];
1304 $sample = isset($conf['sample.']) ? $this->stdWrap($conf['sample'], $conf['sample.']) : $conf['sample'];
1305 if ($sample) {
1306 $parameters['sample'] = 1;
1307 }
1308 foreach ($parameterNames as $parameterName) {
1309 if (isset($conf[$parameterName . '.'])) {
1310 $conf[$parameterName] = $this->stdWrap($conf[$parameterName], $conf[$parameterName . '.']);
1311 }
1312 if (isset($conf[$parameterName]) && $conf[$parameterName]) {
1313 $parameters[$parameterName] = $conf[$parameterName];
1314 }
1315 }
1316 $parametersEncoded = base64_encode(serialize($parameters));
1317 $hmac = GeneralUtility::hmac(implode('|', [$file->getUid(), $parametersEncoded]));
1318 $params = '&md5=' . $hmac;
1319 foreach (str_split($parametersEncoded, 64) as $index => $chunk) {
1320 $params .= '&parameters' . rawurlencode('[') . $index . rawurlencode(']') . '=' . rawurlencode($chunk);
1321 }
1322 $url = $this->getTypoScriptFrontendController()->absRefPrefix . 'index.php?eID=tx_cms_showpic&file=' . $file->getUid() . $params;
1323 $directImageLink = isset($conf['directImageLink.']) ? $this->stdWrap($conf['directImageLink'], $conf['directImageLink.']) : $conf['directImageLink'];
1324 if ($directImageLink) {
1325 $imgResourceConf = [
1326 'file' => $imageFile,
1327 'file.' => $conf
1328 ];
1329 $url = $this->cObjGetSingle('IMG_RESOURCE', $imgResourceConf);
1330 if (!$url) {
1331 // If no imagemagick / gm is available
1332 $url = $imageFile;
1333 }
1334 }
1335 // Create TARGET-attribute only if the right doctype is used
1336 $target = '';
1337 $xhtmlDocType = $this->getTypoScriptFrontendController()->xhtmlDoctype;
1338 if ($xhtmlDocType !== 'xhtml_strict' && $xhtmlDocType !== 'xhtml_11') {
1339 $target = isset($conf['target.'])
1340 ? (string)$this->stdWrap($conf['target'], $conf['target.'])
1341 : (string)$conf['target'];
1342 if ($target === '') {
1343 $target = 'thePicture';
1344 }
1345 }
1346 $a1 = '';
1347 $a2 = '';
1348 $conf['JSwindow'] = isset($conf['JSwindow.']) ? $this->stdWrap($conf['JSwindow'], $conf['JSwindow.']) : $conf['JSwindow'];
1349 if ($conf['JSwindow']) {
1350 if ($conf['JSwindow.']['altUrl'] || $conf['JSwindow.']['altUrl.']) {
1351 $altUrl = isset($conf['JSwindow.']['altUrl.']) ? $this->stdWrap($conf['JSwindow.']['altUrl'], $conf['JSwindow.']['altUrl.']) : $conf['JSwindow.']['altUrl'];
1352 if ($altUrl) {
1353 $url = $altUrl . ($conf['JSwindow.']['altUrl_noDefaultParams'] ? '' : '?file=' . rawurlencode($imageFile) . $params);
1354 }
1355 }
1356
1357 $processedFile = $file->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $conf);
1358 $JSwindowExpand = isset($conf['JSwindow.']['expand.']) ? $this->stdWrap($conf['JSwindow.']['expand'], $conf['JSwindow.']['expand.']) : $conf['JSwindow.']['expand'];
1359 $offset = GeneralUtility::intExplode(',', $JSwindowExpand . ',');
1360 $newWindow = isset($conf['JSwindow.']['newWindow.']) ? $this->stdWrap($conf['JSwindow.']['newWindow'], $conf['JSwindow.']['newWindow.']) : $conf['JSwindow.']['newWindow'];
1361 $onClick = 'openPic('
1362 . GeneralUtility::quoteJSvalue($this->getTypoScriptFrontendController()->baseUrlWrap($url)) . ','
1363 . '\'' . ($newWindow ? md5($url) : 'thePicture') . '\','
1364 . GeneralUtility::quoteJSvalue('width=' . ($processedFile->getProperty('width') + $offset[0])
1365 . ',height=' . ($processedFile->getProperty('height') + $offset[1]) . ',status=0,menubar=0')
1366 . '); return false;';
1367 $a1 = '<a href="' . htmlspecialchars($url) . '"'
1368 . ' onclick="' . htmlspecialchars($onClick) . '"'
1369 . ($target !== '' ? ' target="' . htmlspecialchars($target) . '"' : '')
1370 . $this->getTypoScriptFrontendController()->ATagParams . '>';
1371 $a2 = '</a>';
1372 $this->getTypoScriptFrontendController()->setJS('openPic');
1373 } else {
1374 $conf['linkParams.']['parameter'] = $url;
1375 $string = $this->typoLink($string, $conf['linkParams.']);
1376 }
1377 if (isset($conf['stdWrap.'])) {
1378 $string = $this->stdWrap($string, $conf['stdWrap.']);
1379 }
1380 $content = $a1 . $string . $a2;
1381 }
1382 return $content;
1383 }
1384
1385 /**
1386 * Sets the SYS_LASTCHANGED timestamp if input timestamp is larger than current value.
1387 * The SYS_LASTCHANGED timestamp can be used by various caching/indexing applications to determine if the page has new content.
1388 * Therefore you should call this function with the last-changed timestamp of any element you display.
1389 *
1390 * @param int $tstamp Unix timestamp (number of seconds since 1970)
1391 * @see TypoScriptFrontendController::setSysLastChanged()
1392 */
1393 public function lastChanged($tstamp)
1394 {
1395 $tstamp = (int)$tstamp;
1396 $tsfe = $this->getTypoScriptFrontendController();
1397 if ($tstamp > (int)$tsfe->register['SYS_LASTCHANGED']) {
1398 $tsfe->register['SYS_LASTCHANGED'] = $tstamp;
1399 }
1400 }
1401
1402 /**
1403 * Wraps the input string by the $wrap value and implements the "linkWrap" data type as well.
1404 * The "linkWrap" data type means that this function will find any integer encapsulated in {} (curly braces) in the first wrap part and substitute it with the corresponding page uid from the rootline where the found integer is pointing to the key in the rootline. See link below.
1405 *
1406 * @param string $content Input string
1407 * @param string $wrap A string where the first two parts separated by "|" (vertical line) will be wrapped around the input string
1408 * @return string Wrapped output string
1409 * @see wrap(), cImage(), FILE()
1410 */
1411 public function linkWrap($content, $wrap)
1412 {
1413 $wrapArr = explode('|', $wrap);
1414 if (preg_match('/\\{([0-9]*)\\}/', $wrapArr[0], $reg)) {
1415 $uid = $this->getTypoScriptFrontendController()->tmpl->rootLine[$reg[1]]['uid'] ?? null;
1416 if ($uid) {
1417 $wrapArr[0] = str_replace($reg[0], $uid, $wrapArr[0]);
1418 }
1419 }
1420 return trim($wrapArr[0] ?? '') . $content . trim($wrapArr[1] ?? '');
1421 }
1422
1423 /**
1424 * An abstraction method which creates an alt or title parameter for an HTML img, applet, area or input element and the FILE content element.
1425 * From the $conf array it implements the properties "altText", "titleText" and "longdescURL"
1426 *
1427 * @param array $conf TypoScript configuration properties
1428 * @param bool $longDesc If set, the longdesc attribute will be generated - must only be used for img elements!
1429 * @return string Parameter string containing alt and title parameters (if any)
1430 * @see IMGTEXT(), FILE(), FORM(), cImage(), filelink()
1431 */
1432 public function getAltParam($conf, $longDesc = true)
1433 {
1434 $altText = isset($conf['altText.']) ? trim($this->stdWrap($conf['altText'], $conf['altText.'])) : trim($conf['altText']);
1435 $titleText = isset($conf['titleText.']) ? trim($this->stdWrap($conf['titleText'], $conf['titleText.'])) : trim($conf['titleText']);
1436 if (isset($conf['longdescURL.']) && $this->getTypoScriptFrontendController()->config['config']['doctype'] !== 'html5') {
1437 $longDescUrl = $this->typoLink_URL($conf['longdescURL.']);
1438 } else {
1439 $longDescUrl = trim($conf['longdescURL']);
1440 }
1441 $longDescUrl = strip_tags($longDescUrl);
1442
1443 // "alt":
1444 $altParam = ' alt="' . htmlspecialchars($altText) . '"';
1445 // "title":
1446 $emptyTitleHandling = isset($conf['emptyTitleHandling.']) ? $this->stdWrap($conf['emptyTitleHandling'], $conf['emptyTitleHandling.']) : $conf['emptyTitleHandling'];
1447 // Choices: 'keepEmpty' | 'useAlt' | 'removeAttr'
1448 if ($titleText || $emptyTitleHandling === 'keepEmpty') {
1449 $altParam .= ' title="' . htmlspecialchars($titleText) . '"';
1450 } elseif (!$titleText && $emptyTitleHandling === 'useAlt') {
1451 $altParam .= ' title="' . htmlspecialchars($altText) . '"';
1452 }
1453 // "longDesc" URL
1454 if ($longDesc && !empty($longDescUrl)) {
1455 $altParam .= ' longdesc="' . htmlspecialchars($longDescUrl) . '"';
1456 }
1457 return $altParam;
1458 }
1459
1460 /**
1461 * An abstraction method to add parameters to an A tag.
1462 * Uses the ATagParams property.
1463 *
1464 * @param array $conf TypoScript configuration properties
1465 * @param bool|int $addGlobal If set, will add the global config.ATagParams to the link
1466 * @return string String containing the parameters to the A tag (if non empty, with a leading space)
1467 * @see IMGTEXT(), filelink(), makelinks(), typolink()
1468 */
1469 public function getATagParams($conf, $addGlobal = 1)
1470 {
1471 $aTagParams = '';
1472 if ($conf['ATagParams.'] ?? false) {
1473 $aTagParams = ' ' . $this->stdWrap($conf['ATagParams'], $conf['ATagParams.']);
1474 } elseif ($conf['ATagParams'] ?? false) {
1475 $aTagParams = ' ' . $conf['ATagParams'];
1476 }
1477 if ($addGlobal) {
1478 $aTagParams = ' ' . trim($this->getTypoScriptFrontendController()->ATagParams . $aTagParams);
1479 }
1480 // Extend params
1481 $_params = [
1482 'conf' => &$conf,
1483 'aTagParams' => &$aTagParams
1484 ];
1485 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getATagParamsPostProc'] ?? [] as $className) {
1486 $processor = & GeneralUtility::makeInstance($className);
1487 $aTagParams = $processor->process($_params, $this);
1488 }
1489
1490 $aTagParams = trim($aTagParams);
1491 if (!empty($aTagParams)) {
1492 $aTagParams = ' ' . $aTagParams;
1493 }
1494
1495 return $aTagParams;
1496 }
1497
1498 /**
1499 * All extension links should ask this function for additional properties to their tags.
1500 * Designed to add for instance an "onclick" property for site tracking systems.
1501 *
1502 * @param string $URL URL of the website
1503 * @param string $TYPE
1504 * @return string The additional tag properties
1505 */
1506 public function extLinkATagParams($URL, $TYPE)
1507 {
1508 $out = '';
1509 if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['extLinkATagParamsHandler'])) {
1510 $extLinkATagParamsHandler = GeneralUtility::makeInstance($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['extLinkATagParamsHandler']);
1511 if (method_exists($extLinkATagParamsHandler, 'main')) {
1512 $out .= trim($extLinkATagParamsHandler->main($URL, $TYPE, $this));
1513 }
1514 }
1515 return trim($out) ? ' ' . trim($out) : '';
1516 }
1517
1518 /***********************************************
1519 *
1520 * HTML template processing functions
1521 *
1522 ***********************************************/
1523
1524 /**
1525 * Sets the current file object during iterations over files.
1526 *
1527 * @param File $fileObject The file object.
1528 */
1529 public function setCurrentFile($fileObject)
1530 {
1531 $this->currentFile = $fileObject;
1532 }
1533
1534 /**
1535 * Gets the current file object during iterations over files.
1536 *
1537 * @return File The current file object.
1538 */
1539 public function getCurrentFile()
1540 {
1541 return $this->currentFile;
1542 }
1543
1544 /***********************************************
1545 *
1546 * "stdWrap" + sub functions
1547 *
1548 ***********************************************/
1549 /**
1550 * The "stdWrap" function. This is the implementation of what is known as "stdWrap properties" in TypoScript.
1551 * Basically "stdWrap" performs some processing of a value based on properties in the input $conf array(holding the TypoScript "stdWrap properties")
1552 * 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.
1553 *
1554 * If $this->alternativeData is an array it's used instead of the $this->data array in ->getData
1555 *
1556 * @param string $content Input value undergoing processing in this function. Possibly substituted by other values fetched from another source.
1557 * @param array $conf TypoScript "stdWrap properties".
1558 * @return string The processed input value
1559 */
1560 public function stdWrap($content = '', $conf = [])
1561 {
1562 $content = (string)$content;
1563 // If there is any hook object, activate all of the process and override functions.
1564 // The hook interface ContentObjectStdWrapHookInterface takes care that all 4 methods exist.
1565 if ($this->stdWrapHookObjects) {
1566 $conf['stdWrapPreProcess'] = 1;
1567 $conf['stdWrapOverride'] = 1;
1568 $conf['stdWrapProcess'] = 1;
1569 $conf['stdWrapPostProcess'] = 1;
1570 }
1571
1572 if (!is_array($conf) || !$conf) {
1573 return $content;
1574 }
1575
1576 // Cache handling
1577 if (isset($conf['cache.']) && is_array($conf['cache.'])) {
1578 $conf['cache.']['key'] = $this->stdWrap($conf['cache.']['key'], $conf['cache.']['key.']);
1579 $conf['cache.']['tags'] = $this->stdWrap($conf['cache.']['tags'], $conf['cache.']['tags.']);
1580 $conf['cache.']['lifetime'] = $this->stdWrap($conf['cache.']['lifetime'], $conf['cache.']['lifetime.']);
1581 $conf['cacheRead'] = 1;
1582 $conf['cacheStore'] = 1;
1583 }
1584 // The configuration is sorted and filtered by intersection with the defined stdWrapOrder.
1585 $sortedConf = array_keys(array_intersect_key($this->stdWrapOrder, $conf));
1586 // Functions types that should not make use of nested stdWrap function calls to avoid conflicts with internal TypoScript used by these functions
1587 $stdWrapDisabledFunctionTypes = 'cObject,functionName,stdWrap';
1588 // Additional Array to check whether a function has already been executed
1589 $isExecuted = [];
1590 // Additional switch to make sure 'required', 'if' and 'fieldRequired'
1591 // will still stop rendering immediately in case they return FALSE
1592 $this->stdWrapRecursionLevel++;
1593 $this->stopRendering[$this->stdWrapRecursionLevel] = false;
1594 // execute each function in the predefined order
1595 foreach ($sortedConf as $stdWrapName) {
1596 // eliminate the second key of a pair 'key'|'key.' to make sure functions get called only once and check if rendering has been stopped
1597 if ((!isset($isExecuted[$stdWrapName]) || !$isExecuted[$stdWrapName]) && !$this->stopRendering[$this->stdWrapRecursionLevel]) {
1598 $functionName = rtrim($stdWrapName, '.');
1599 $functionProperties = $functionName . '.';
1600 $functionType = $this->stdWrapOrder[$functionName] ?? null;
1601 // If there is any code on the next level, check if it contains "official" stdWrap functions
1602 // if yes, execute them first - will make each function stdWrap aware
1603 // so additional stdWrap calls within the functions can be removed, since the result will be the same
1604 if (!empty($conf[$functionProperties]) && !GeneralUtility::inList($stdWrapDisabledFunctionTypes, $functionType)) {
1605 if (array_intersect_key($this->stdWrapOrder, $conf[$functionProperties])) {
1606 $conf[$functionName] = $this->stdWrap($conf[$functionName] ?? '', $conf[$functionProperties] ?? []);
1607 }
1608 }
1609 // Check if key is still containing something, since it might have been changed by next level stdWrap before
1610 if ((isset($conf[$functionName]) || $conf[$functionProperties])
1611 && ($functionType !== 'boolean' || $conf[$functionName])
1612 ) {
1613 // Get just that part of $conf that is needed for the particular function
1614 $singleConf = [
1615 $functionName => $conf[$functionName] ?? null,
1616 $functionProperties => $conf[$functionProperties] ?? null
1617 ];
1618 // Hand over the whole $conf array to the stdWrapHookObjects
1619 if ($functionType === 'hook') {
1620 $singleConf = $conf;
1621 }
1622 // Add both keys - with and without the dot - to the set of executed functions
1623 $isExecuted[$functionName] = true;
1624 $isExecuted[$functionProperties] = true;
1625 // Call the function with the prefix stdWrap_ to make sure nobody can execute functions just by adding their name to the TS Array
1626 $functionName = 'stdWrap_' . $functionName;
1627 $content = $this->{$functionName}($content, $singleConf);
1628 } elseif ($functionType === 'boolean' && !$conf[$functionName]) {
1629 $isExecuted[$functionName] = true;
1630 $isExecuted[$functionProperties] = true;
1631 }
1632 }
1633 }
1634 unset($this->stopRendering[$this->stdWrapRecursionLevel]);
1635 $this->stdWrapRecursionLevel--;
1636
1637 return $content;
1638 }
1639
1640 /**
1641 * Gets a configuration value by passing them through stdWrap first and taking a default value if stdWrap doesn't yield a result.
1642 *
1643 * @param string $key The config variable key (from TS array).
1644 * @param array $config The TypoScript array.
1645 * @param string $defaultValue Optional default value.
1646 * @return string Value of the config variable
1647 */
1648 public function stdWrapValue($key, array $config, $defaultValue = '')
1649 {
1650 if (isset($config[$key])) {
1651 if (!isset($config[$key . '.'])) {
1652 return $config[$key];
1653 }
1654 } elseif (isset($config[$key . '.'])) {
1655 $config[$key] = '';
1656 } else {
1657 return $defaultValue;
1658 }
1659 $stdWrapped = $this->stdWrap($config[$key], $config[$key . '.']);
1660 return $stdWrapped ?: $defaultValue;
1661 }
1662
1663 /**
1664 * stdWrap pre process hook
1665 * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
1666 * this hook will execute functions before any other stdWrap function can modify anything
1667 *
1668 * @param string $content Input value undergoing processing in these functions.
1669 * @param array $conf All stdWrap properties, not just the ones for a particular function.
1670 * @return string The processed input value
1671 */
1672 public function stdWrap_stdWrapPreProcess($content = '', $conf = [])
1673 {
1674 foreach ($this->stdWrapHookObjects as $hookObject) {
1675 /** @var ContentObjectStdWrapHookInterface $hookObject */
1676 $content = $hookObject->stdWrapPreProcess($content, $conf, $this);
1677 }
1678 return $content;
1679 }
1680
1681 /**
1682 * Check if content was cached before (depending on the given cache key)
1683 *
1684 * @param string $content Input value undergoing processing in these functions.
1685 * @param array $conf All stdWrap properties, not just the ones for a particular function.
1686 * @return string The processed input value
1687 */
1688 public function stdWrap_cacheRead($content = '', $conf = [])
1689 {
1690 if (!isset($conf['cache.'])) {
1691 return $content;
1692 }
1693 $result = $this->getFromCache($conf['cache.']);
1694 return $result === false ? $content : $result;
1695 }
1696
1697 /**
1698 * Add tags to page cache (comma-separated list)
1699 *
1700 * @param string $content Input value undergoing processing in these functions.
1701 * @param array $conf All stdWrap properties, not just the ones for a particular function.
1702 * @return string The processed input value
1703 */
1704 public function stdWrap_addPageCacheTags($content = '', $conf = [])
1705 {
1706 $tags = isset($conf['addPageCacheTags.'])
1707 ? $this->stdWrap($conf['addPageCacheTags'], $conf['addPageCacheTags.'])
1708 : $conf['addPageCacheTags'];
1709 if (!empty($tags)) {
1710 $cacheTags = GeneralUtility::trimExplode(',', $tags, true);
1711 $this->getTypoScriptFrontendController()->addCacheTags($cacheTags);
1712 }
1713 return $content;
1714 }
1715
1716 /**
1717 * setContentToCurrent
1718 * actually it just does the contrary: Sets the value of 'current' based on current content
1719 *
1720 * @param string $content Input value undergoing processing in this function.
1721 * @return string The processed input value
1722 */
1723 public function stdWrap_setContentToCurrent($content = '')
1724 {
1725 $this->data[$this->currentValKey] = $content;
1726 return $content;
1727 }
1728
1729 /**
1730 * setCurrent
1731 * Sets the value of 'current' based on the outcome of stdWrap operations
1732 *
1733 * @param string $content Input value undergoing processing in this function.
1734 * @param array $conf stdWrap properties for setCurrent.
1735 * @return string The processed input value
1736 */
1737 public function stdWrap_setCurrent($content = '', $conf = [])
1738 {
1739 $this->data[$this->currentValKey] = $conf['setCurrent'] ?? null;
1740 return $content;
1741 }
1742
1743 /**
1744 * lang
1745 * Translates content based on the language currently used by the FE
1746 *
1747 * @param string $content Input value undergoing processing in this function.
1748 * @param array $conf stdWrap properties for lang.
1749 * @return string The processed input value
1750 */
1751 public function stdWrap_lang($content = '', $conf = [])
1752 {
1753 $tsfe = $this->getTypoScriptFrontendController();
1754 if (
1755 isset($conf['lang.'])
1756 && isset($tsfe->config['config']['language'])
1757 && $tsfe->config['config']['language']
1758 && isset($conf['lang.'][$tsfe->config['config']['language']])
1759 ) {
1760 $content = $conf['lang.'][$tsfe->config['config']['language']];
1761 }
1762 return $content;
1763 }
1764
1765 /**
1766 * data
1767 * Gets content from different sources based on getText functions, makes use of alternativeData, when set
1768 *
1769 * @param string $content Input value undergoing processing in this function.
1770 * @param array $conf stdWrap properties for data.
1771 * @return string The processed input value
1772 */
1773 public function stdWrap_data($content = '', $conf = [])
1774 {
1775 $content = $this->getData($conf['data'], is_array($this->alternativeData) ? $this->alternativeData : $this->data);
1776 // This must be unset directly after
1777 $this->alternativeData = '';
1778 return $content;
1779 }
1780
1781 /**
1782 * field
1783 * Gets content from a DB field
1784 *
1785 * @param string $content Input value undergoing processing in this function.
1786 * @param array $conf stdWrap properties for field.
1787 * @return string The processed input value
1788 */
1789 public function stdWrap_field($content = '', $conf = [])
1790 {
1791 return $this->getFieldVal($conf['field']);
1792 }
1793
1794 /**
1795 * current
1796 * Gets content that has been perviously set as 'current'
1797 * Can be set via setContentToCurrent or setCurrent or will be set automatically i.e. inside the split function
1798 *
1799 * @param string $content Input value undergoing processing in this function.
1800 * @param array $conf stdWrap properties for current.
1801 * @return string The processed input value
1802 */
1803 public function stdWrap_current($content = '', $conf = [])
1804 {
1805 return $this->data[$this->currentValKey];
1806 }
1807
1808 /**
1809 * cObject
1810 * Will replace the content with the value of an official TypoScript cObject
1811 * like TEXT, COA, HMENU
1812 *
1813 * @param string $content Input value undergoing processing in this function.
1814 * @param array $conf stdWrap properties for cObject.
1815 * @return string The processed input value
1816 */
1817 public function stdWrap_cObject($content = '', $conf = [])
1818 {
1819 return $this->cObjGetSingle($conf['cObject'] ?? '', $conf['cObject.'] ?? [], '/stdWrap/.cObject');
1820 }
1821
1822 /**
1823 * numRows
1824 * Counts the number of returned records of a DB operation
1825 * makes use of select internally
1826 *
1827 * @param string $content Input value undergoing processing in this function.
1828 * @param array $conf stdWrap properties for numRows.
1829 * @return string The processed input value
1830 */
1831 public function stdWrap_numRows($content = '', $conf = [])
1832 {
1833 return $this->numRows($conf['numRows.']);
1834 }
1835
1836 /**
1837 * filelist
1838 * Will create a list of files based on some additional parameters
1839 *
1840 * @param string $content Input value undergoing processing in this function.
1841 * @param array $conf stdWrap properties for filelist.
1842 * @return string The processed input value
1843 * @deprecated since TYPO3 v9.5, will be removed in TYPO3 v10.0. Use cObject FILES instead.
1844 */
1845 public function stdWrap_filelist($content = '', $conf = [])
1846 {
1847 return $this->filelist($conf['filelist'], true);
1848 }
1849
1850 /**
1851 * preUserFunc
1852 * Will execute a user public function before the content will be modified by any other stdWrap function
1853 *
1854 * @param string $content Input value undergoing processing in this function.
1855 * @param array $conf stdWrap properties for preUserFunc.
1856 * @return string The processed input value
1857 */
1858 public function stdWrap_preUserFunc($content = '', $conf = [])
1859 {
1860 return $this->callUserFunction($conf['preUserFunc'], $conf['preUserFunc.'], $content);
1861 }
1862
1863 /**
1864 * stdWrap override hook
1865 * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
1866 * this hook will execute functions on existing content but still before the content gets modified or replaced
1867 *
1868 * @param string $content Input value undergoing processing in these functions.
1869 * @param array $conf All stdWrap properties, not just the ones for a particular function.
1870 * @return string The processed input value
1871 */
1872 public function stdWrap_stdWrapOverride($content = '', $conf = [])
1873 {
1874 foreach ($this->stdWrapHookObjects as $hookObject) {
1875 /** @var ContentObjectStdWrapHookInterface $hookObject */
1876 $content = $hookObject->stdWrapOverride($content, $conf, $this);
1877 }
1878 return $content;
1879 }
1880
1881 /**
1882 * override
1883 * Will override the current value of content with its own value'
1884 *
1885 * @param string $content Input value undergoing processing in this function.
1886 * @param array $conf stdWrap properties for override.
1887 * @return string The processed input value
1888 */
1889 public function stdWrap_override($content = '', $conf = [])
1890 {
1891 if (trim($conf['override'] ?? false)) {
1892 $content = $conf['override'];
1893 }
1894 return $content;
1895 }
1896
1897 /**
1898 * preIfEmptyListNum
1899 * Gets a value off a CSV list before the following ifEmpty check
1900 * 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
1901 *
1902 * @param string $content Input value undergoing processing in this function.
1903 * @param array $conf stdWrap properties for preIfEmptyListNum.
1904 * @return string The processed input value
1905 */
1906 public function stdWrap_preIfEmptyListNum($content = '', $conf = [])
1907 {
1908 return $this->listNum($content, $conf['preIfEmptyListNum'] ?? null, $conf['preIfEmptyListNum.']['splitChar'] ?? null);
1909 }
1910
1911 /**
1912 * ifNull
1913 * Will set content to a replacement value in case the value of content is NULL
1914 *
1915 * @param string|null $content Input value undergoing processing in this function.
1916 * @param array $conf stdWrap properties for ifNull.
1917 * @return string The processed input value
1918 */
1919 public function stdWrap_ifNull($content = '', $conf = [])
1920 {
1921 return $content ?? $conf['ifNull'];
1922 }
1923
1924 /**
1925 * ifEmpty
1926 * Will set content to a replacement value in case the trimmed value of content returns FALSE
1927 * 0 (zero) will be replaced as well
1928 *
1929 * @param string $content Input value undergoing processing in this function.
1930 * @param array $conf stdWrap properties for ifEmpty.
1931 * @return string The processed input value
1932 */
1933 public function stdWrap_ifEmpty($content = '', $conf = [])
1934 {
1935 if (!trim($content)) {
1936 $content = $conf['ifEmpty'];
1937 }
1938 return $content;
1939 }
1940
1941 /**
1942 * ifBlank
1943 * Will set content to a replacement value in case the trimmed value of content has no length
1944 * 0 (zero) will not be replaced
1945 *
1946 * @param string $content Input value undergoing processing in this function.
1947 * @param array $conf stdWrap properties for ifBlank.
1948 * @return string The processed input value
1949 */
1950 public function stdWrap_ifBlank($content = '', $conf = [])
1951 {
1952 if (trim($content) === '') {
1953 $content = $conf['ifBlank'];
1954 }
1955 return $content;
1956 }
1957
1958 /**
1959 * listNum
1960 * Gets a value off a CSV list after ifEmpty check
1961 * Might return an empty value in case the CSV does not contain a value at the position given by listNum
1962 * Use preIfEmptyListNum to avoid that behaviour
1963 *
1964 * @param string $content Input value undergoing processing in this function.
1965 * @param array $conf stdWrap properties for listNum.
1966 * @return string The processed input value
1967 */
1968 public function stdWrap_listNum($content = '', $conf = [])
1969 {
1970 return $this->listNum($content, $conf['listNum'] ?? null, $conf['listNum.']['splitChar'] ?? null);
1971 }
1972
1973 /**
1974 * trim
1975 * Cuts off any whitespace at the beginning and the end of the content
1976 *
1977 * @param string $content Input value undergoing processing in this function.
1978 * @return string The processed input value
1979 */
1980 public function stdWrap_trim($content = '')
1981 {
1982 return trim($content);
1983 }
1984
1985 /**
1986 * strPad
1987 * Will return a string padded left/right/on both sides, based on configuration given as stdWrap properties
1988 *
1989 * @param string $content Input value undergoing processing in this function.
1990 * @param array $conf stdWrap properties for strPad.
1991 * @return string The processed input value
1992 */
1993 public function stdWrap_strPad($content = '', $conf = [])
1994 {
1995 // Must specify a length in conf for this to make sense
1996 $length = 0;
1997 // Padding with space is PHP-default
1998 $padWith = ' ';
1999 // Padding on the right side is PHP-default
2000 $padType = STR_PAD_RIGHT;
2001 if (!empty($conf['strPad.']['length'])) {
2002 $length = isset($conf['strPad.']['length.']) ? $this->stdWrap($conf['strPad.']['length'], $conf['strPad.']['length.']) : $conf['strPad.']['length'];
2003 $length = (int)$length;
2004 }
2005 if (isset($conf['strPad.']['padWith']) && (string)$conf['strPad.']['padWith'] !== '') {
2006 $padWith = isset($conf['strPad.']['padWith.']) ? $this->stdWrap($conf['strPad.']['padWith'], $conf['strPad.']['padWith.']) : $conf['strPad.']['padWith'];
2007 }
2008 if (!empty($conf['strPad.']['type'])) {
2009 $type = isset($conf['strPad.']['type.']) ? $this->stdWrap($conf['strPad.']['type'], $conf['strPad.']['type.']) : $conf['strPad.']['type'];
2010 if (strtolower($type) === 'left') {
2011 $padType = STR_PAD_LEFT;
2012 } elseif (strtolower($type) === 'both') {
2013 $padType = STR_PAD_BOTH;
2014 }
2015 }
2016 return str_pad($content, $length, $padWith, $padType);
2017 }
2018
2019 /**
2020 * stdWrap
2021 * A recursive call of the stdWrap function set
2022 * This enables the user to execute stdWrap functions in another than the predefined order
2023 * It modifies the content, not the property
2024 * while the new feature of chained stdWrap functions modifies the property and not the content
2025 *
2026 * @param string $content Input value undergoing processing in this function.
2027 * @param array $conf stdWrap properties for stdWrap.
2028 * @return string The processed input value
2029 */
2030 public function stdWrap_stdWrap($content = '', $conf = [])
2031 {
2032 return $this->stdWrap($content, $conf['stdWrap.']);
2033 }
2034
2035 /**
2036 * stdWrap process hook
2037 * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
2038 * this hook executes functions directly after the recursive stdWrap function call but still before the content gets modified
2039 *
2040 * @param string $content Input value undergoing processing in these functions.
2041 * @param array $conf All stdWrap properties, not just the ones for a particular function.
2042 * @return string The processed input value
2043 */
2044 public function stdWrap_stdWrapProcess($content = '', $conf = [])
2045 {
2046 foreach ($this->stdWrapHookObjects as $hookObject) {
2047 /** @var ContentObjectStdWrapHookInterface $hookObject */
2048 $content = $hookObject->stdWrapProcess($content, $conf, $this);
2049 }
2050 return $content;
2051 }
2052
2053 /**
2054 * required
2055 * Will immediately stop rendering and return an empty value
2056 * when there is no content at this point
2057 *
2058 * @param string $content Input value undergoing processing in this function.
2059 * @return string The processed input value
2060 */
2061 public function stdWrap_required($content = '')
2062 {
2063 if ((string)$content === '') {
2064 $content = '';
2065 $this->stopRendering[$this->stdWrapRecursionLevel] = true;
2066 }
2067 return $content;
2068 }
2069
2070 /**
2071 * if
2072 * Will immediately stop rendering and return an empty value
2073 * when the result of the checks returns FALSE
2074 *
2075 * @param string $content Input value undergoing processing in this function.
2076 * @param array $conf stdWrap properties for if.
2077 * @return string The processed input value
2078 */
2079 public function stdWrap_if($content = '', $conf = [])
2080 {
2081 if (empty($conf['if.']) || $this->checkIf($conf['if.'])) {
2082 return $content;
2083 }
2084 $this->stopRendering[$this->stdWrapRecursionLevel] = true;
2085 return '';
2086 }
2087
2088 /**
2089 * fieldRequired
2090 * Will immediately stop rendering and return an empty value
2091 * when there is no content in the field given by fieldRequired
2092 *
2093 * @param string $content Input value undergoing processing in this function.
2094 * @param array $conf stdWrap properties for fieldRequired.
2095 * @return string The processed input value
2096 */
2097 public function stdWrap_fieldRequired($content = '', $conf = [])
2098 {
2099 if (!trim($this->data[$conf['fieldRequired'] ?? null] ?? '')) {
2100 $content = '';
2101 $this->stopRendering[$this->stdWrapRecursionLevel] = true;
2102 }
2103 return $content;
2104 }
2105
2106 /**
2107 * stdWrap csConv: Converts the input to UTF-8
2108 *
2109 * The character set of the input must be specified. Returns the input if
2110 * matters go wrong, for example if an invalid character set is given.
2111 *
2112 * @param string $content The string to convert.
2113 * @param array $conf stdWrap properties for csConv.
2114 * @return string The processed input.
2115 */
2116 public function stdWrap_csConv($content = '', $conf = [])
2117 {
2118 if (!empty($conf['csConv'])) {
2119 $output = mb_convert_encoding($content, 'utf-8', trim(strtolower($conf['csConv'])));
2120 return $output !== false && $output !== '' ? $output : $content;
2121 }
2122 return $content;
2123 }
2124
2125 /**
2126 * parseFunc
2127 * Will parse the content based on functions given as stdWrap properties
2128 * Heavily used together with RTE based content
2129 *
2130 * @param string $content Input value undergoing processing in this function.
2131 * @param array $conf stdWrap properties for parseFunc.
2132 * @return string The processed input value
2133 */
2134 public function stdWrap_parseFunc($content = '', $conf = [])
2135 {
2136 return $this->parseFunc($content, $conf['parseFunc.'], $conf['parseFunc']);
2137 }
2138
2139 /**
2140 * HTMLparser
2141 * Will parse HTML content based on functions given as stdWrap properties
2142 * Heavily used together with RTE based content
2143 *
2144 * @param string $content Input value undergoing processing in this function.
2145 * @param array $conf stdWrap properties for HTMLparser.
2146 * @return string The processed input value
2147 */
2148 public function stdWrap_HTMLparser($content = '', $conf = [])
2149 {
2150 if (isset($conf['HTMLparser.']) && is_array($conf['HTMLparser.'])) {
2151 $content = $this->HTMLparser_TSbridge($content, $conf['HTMLparser.']);
2152 }
2153 return $content;
2154 }
2155
2156 /**
2157 * split
2158 * Will split the content by a given token and treat the results separately
2159 * Automatically fills 'current' with a single result
2160 *
2161 * @param string $content Input value undergoing processing in this function.
2162 * @param array $conf stdWrap properties for split.
2163 * @return string The processed input value
2164 */
2165 public function stdWrap_split($content = '', $conf = [])
2166 {
2167 return $this->splitObj($content, $conf['split.']);
2168 }
2169
2170 /**
2171 * replacement
2172 * Will execute replacements on the content (optionally with preg-regex)
2173 *
2174 * @param string $content Input value undergoing processing in this function.
2175 * @param array $conf stdWrap properties for replacement.
2176 * @return string The processed input value
2177 */
2178 public function stdWrap_replacement($content = '', $conf = [])
2179 {
2180 return $this->replacement($content, $conf['replacement.']);
2181 }
2182
2183 /**
2184 * prioriCalc
2185 * Will use the content as a mathematical term and calculate the result
2186 * Can be set to 1 to just get a calculated value or 'intval' to get the integer of the result
2187 *
2188 * @param string $content Input value undergoing processing in this function.
2189 * @param array $conf stdWrap properties for prioriCalc.
2190 * @return string The processed input value
2191 */
2192 public function stdWrap_prioriCalc($content = '', $conf = [])
2193 {
2194 $content = MathUtility::calculateWithParentheses($content);
2195 if (!empty($conf['prioriCalc']) && $conf['prioriCalc'] === 'intval') {
2196 $content = (int)$content;
2197 }
2198 return $content;
2199 }
2200
2201 /**
2202 * char
2203 * Returns a one-character string containing the character specified by ascii code.
2204 *
2205 * Reliable results only for character codes in the integer range 0 - 127.
2206 *
2207 * @see http://php.net/manual/en/function.chr.php
2208 * @param string $content Input value undergoing processing in this function.
2209 * @param array $conf stdWrap properties for char.
2210 * @return string The processed input value
2211 */
2212 public function stdWrap_char($content = '', $conf = [])
2213 {
2214 return chr((int)$conf['char']);
2215 }
2216
2217 /**
2218 * intval
2219 * Will return an integer value of the current content
2220 *
2221 * @param string $content Input value undergoing processing in this function.
2222 * @return string The processed input value
2223 */
2224 public function stdWrap_intval($content = '')
2225 {
2226 return (int)$content;
2227 }
2228
2229 /**
2230 * Will return a hashed value of the current content
2231 *
2232 * @param string $content Input value undergoing processing in this function.
2233 * @param array $conf stdWrap properties for hash.
2234 * @return string The processed input value
2235 * @link http://php.net/manual/de/function.hash-algos.php for a list of supported hash algorithms
2236 */
2237 public function stdWrap_hash($content = '', array $conf = [])
2238 {
2239 $algorithm = isset($conf['hash.']) ? $this->stdWrap($conf['hash'], $conf['hash.']) : $conf['hash'];
2240 if (function_exists('hash') && in_array($algorithm, hash_algos())) {
2241 return hash($algorithm, $content);
2242 }
2243 // Non-existing hashing algorithm
2244 return '';
2245 }
2246
2247 /**
2248 * stdWrap_round will return a rounded number with ceil(), floor() or round(), defaults to round()
2249 * Only the english number format is supported . (dot) as decimal point
2250 *
2251 * @param string $content Input value undergoing processing in this function.
2252 * @param array $conf stdWrap properties for round.
2253 * @return string The processed input value
2254 */
2255 public function stdWrap_round($content = '', $conf = [])
2256 {
2257 return $this->round($content, $conf['round.']);
2258 }
2259
2260 /**
2261 * numberFormat
2262 * Will return a formatted number based on configuration given as stdWrap properties
2263 *
2264 * @param string $content Input value undergoing processing in this function.
2265 * @param array $conf stdWrap properties for numberFormat.
2266 * @return string The processed input value
2267 */
2268 public function stdWrap_numberFormat($content = '', $conf = [])
2269 {
2270 return $this->numberFormat($content, $conf['numberFormat.'] ?? []);
2271 }
2272
2273 /**
2274 * expandList
2275 * Will return a formatted number based on configuration given as stdWrap properties
2276 *
2277 * @param string $content Input value undergoing processing in this function.
2278 * @return string The processed input value
2279 */
2280 public function stdWrap_expandList($content = '')
2281 {
2282 return GeneralUtility::expandList($content);
2283 }
2284
2285 /**
2286 * date
2287 * Will return a formatted date based on configuration given according to PHP date/gmdate properties
2288 * Will return gmdate when the property GMT returns TRUE
2289 *
2290 * @param string $content Input value undergoing processing in this function.
2291 * @param array $conf stdWrap properties for date.
2292 * @return string The processed input value
2293 */
2294 public function stdWrap_date($content = '', $conf = [])
2295 {
2296 // Check for zero length string to mimic default case of date/gmdate.
2297 $content = (string)$content === '' ? $GLOBALS['EXEC_TIME'] : (int)$content;
2298 $content = !empty($conf['date.']['GMT']) ? gmdate($conf['date'] ?? null, $content) : date($conf['date'] ?? null, $content);
2299 return $content;
2300 }
2301
2302 /**
2303 * strftime
2304 * Will return a formatted date based on configuration given according to PHP strftime/gmstrftime properties
2305 * Will return gmstrftime when the property GMT returns TRUE
2306 *
2307 * @param string $content Input value undergoing processing in this function.
2308 * @param array $conf stdWrap properties for strftime.
2309 * @return string The processed input value
2310 */
2311 public function stdWrap_strftime($content = '', $conf = [])
2312 {
2313 // Check for zero length string to mimic default case of strtime/gmstrftime
2314 $content = (string)$content === '' ? $GLOBALS['EXEC_TIME'] : (int)$content;
2315 $content = (isset($conf['strftime.']['GMT']) && $conf['strftime.']['GMT'])
2316 ? gmstrftime($conf['strftime'] ?? null, $content)
2317 : strftime($conf['strftime'] ?? null, $content);
2318 if (!empty($conf['strftime.']['charset'])) {
2319 $output = mb_convert_encoding($content, 'utf-8', trim(strtolower($conf['strftime.']['charset'])));
2320 return $output ?: $content;
2321 }
2322 return $content;
2323 }
2324
2325 /**
2326 * strtotime
2327 * Will return a timestamp based on configuration given according to PHP strtotime
2328 *
2329 * @param string $content Input value undergoing processing in this function.
2330 * @param array $conf stdWrap properties for strtotime.
2331 * @return string The processed input value
2332 */
2333 public function stdWrap_strtotime($content = '', $conf = [])
2334 {
2335 if ($conf['strtotime'] !== '1') {
2336 $content .= ' ' . $conf['strtotime'];
2337 }
2338 return strtotime($content, $GLOBALS['EXEC_TIME']);
2339 }
2340
2341 /**
2342 * age
2343 * Will return the age of a given timestamp based on configuration given by stdWrap properties
2344 *
2345 * @param string $content Input value undergoing processing in this function.
2346 * @param array $conf stdWrap properties for age.
2347 * @return string The processed input value
2348 */
2349 public function stdWrap_age($content = '', $conf = [])
2350 {
2351 return $this->calcAge((int)($GLOBALS['EXEC_TIME'] ?? 0) - (int)$content, $conf['age'] ?? null);
2352 }
2353
2354 /**
2355 * case
2356 * Will transform the content to be upper or lower case only
2357 * Leaves HTML tags untouched
2358 *
2359 * @param string $content Input value undergoing processing in this function.
2360 * @param array $conf stdWrap properties for case.
2361 * @return string The processed input value
2362 */
2363 public function stdWrap_case($content = '', $conf = [])
2364 {
2365 return $this->HTMLcaseshift($content, $conf['case']);
2366 }
2367
2368 /**
2369 * bytes
2370 * Will return the size of a given number in Bytes *
2371 *
2372 * @param string $content Input value undergoing processing in this function.
2373 * @param array $conf stdWrap properties for bytes.
2374 * @return string The processed input value
2375 */
2376 public function stdWrap_bytes($content = '', $conf = [])
2377 {
2378 return GeneralUtility::formatSize($content, $conf['bytes.']['labels'], $conf['bytes.']['base']);
2379 }
2380
2381 /**
2382 * substring
2383 * Will return a substring based on position information given by stdWrap properties
2384 *
2385 * @param string $content Input value undergoing processing in this function.
2386 * @param array $conf stdWrap properties for substring.
2387 * @return string The processed input value
2388 */
2389 public function stdWrap_substring($content = '', $conf = [])
2390 {
2391 return $this->substring($content, $conf['substring']);
2392 }
2393
2394 /**
2395 * cropHTML
2396 * Crops content to a given size while leaving HTML tags untouched
2397 *
2398 * @param string $content Input value undergoing processing in this function.
2399 * @param array $conf stdWrap properties for cropHTML.
2400 * @return string The processed input value
2401 */
2402 public function stdWrap_cropHTML($content = '', $conf = [])
2403 {
2404 return $this->cropHTML($content, $conf['cropHTML'] ?? '');
2405 }
2406
2407 /**
2408 * stripHtml
2409 * Copmletely removes HTML tags from content
2410 *
2411 * @param string $content Input value undergoing processing in this function.
2412 * @return string The processed input value
2413 */
2414 public function stdWrap_stripHtml($content = '')
2415 {
2416 return strip_tags($content);
2417 }
2418
2419 /**
2420 * crop
2421 * Crops content to a given size without caring about HTML tags
2422 *
2423 * @param string $content Input value undergoing processing in this function.
2424 * @param array $conf stdWrap properties for crop.
2425 * @return string The processed input value
2426 */
2427 public function stdWrap_crop($content = '', $conf = [])
2428 {
2429 return $this->crop($content, $conf['crop']);
2430 }
2431
2432 /**
2433 * rawUrlEncode
2434 * Encodes content to be used within URLs
2435 *
2436 * @param string $content Input value undergoing processing in this function.
2437 * @return string The processed input value
2438 */
2439 public function stdWrap_rawUrlEncode($content = '')
2440 {
2441 return rawurlencode($content);
2442 }
2443
2444 /**
2445 * htmlSpecialChars
2446 * Transforms HTML tags to readable text by replacing special characters with their HTML entity
2447 * When preserveEntities returns TRUE, existing entities will be left untouched
2448 *
2449 * @param string $content Input value undergoing processing in this function.
2450 * @param array $conf stdWrap properties for htmlSpecalChars.
2451 * @return string The processed input value
2452 */
2453 public function stdWrap_htmlSpecialChars($content = '', $conf = [])
2454 {
2455 if (!empty($conf['htmlSpecialChars.']['preserveEntities'])) {
2456 $content = htmlspecialchars($content, ENT_COMPAT, 'UTF-8', false);
2457 } else {
2458 $content = htmlspecialchars($content);
2459 }
2460 return $content;
2461 }
2462
2463 /**
2464 * encodeForJavaScriptValue
2465 * Escapes content to be used inside JavaScript strings. No quotes are added around the value
2466 * as this can easily be done in TypoScript
2467 *
2468 * @param string $content Input value undergoing processing in this function
2469 * @return string The processed input value
2470 */
2471 public function stdWrap_encodeForJavaScriptValue($content = '')
2472 {
2473 return GeneralUtility::quoteJSvalue($content);
2474 }
2475
2476 /**
2477 * doubleBrTag
2478 * Searches for double line breaks and replaces them with the given value
2479 *
2480 * @param string $content Input value undergoing processing in this function.
2481 * @param array $conf stdWrap properties for doubleBrTag.
2482 * @return string The processed input value
2483 */
2484 public function stdWrap_doubleBrTag($content = '', $conf = [])
2485 {
2486 return preg_replace('/\R{1,2}[\t\x20]*\R{1,2}/', $conf['doubleBrTag'] ?? null, $content);
2487 }
2488
2489 /**
2490 * br
2491 * Searches for single line breaks and replaces them with a <br />/<br> tag
2492 * according to the doctype
2493 *
2494 * @param string $content Input value undergoing processing in this function.
2495 * @return string The processed input value
2496 */
2497 public function stdWrap_br($content = '')
2498 {
2499 return nl2br($content, !empty($this->getTypoScriptFrontendController()->xhtmlDoctype));
2500 }
2501
2502 /**
2503 * brTag
2504 * Searches for single line feeds and replaces them with the given value
2505 *
2506 * @param string $content Input value undergoing processing in this function.
2507 * @param array $conf stdWrap properties for brTag.
2508 * @return string The processed input value
2509 */
2510 public function stdWrap_brTag($content = '', $conf = [])
2511 {
2512 return str_replace(LF, $conf['brTag'] ?? null, $content);
2513 }
2514
2515 /**
2516 * encapsLines
2517 * Modifies text blocks by searching for lines which are not surrounded by HTML tags yet
2518 * and wrapping them with values given by stdWrap properties
2519 *
2520 * @param string $content Input value undergoing processing in this function.
2521 * @param array $conf stdWrap properties for erncapsLines.
2522 * @return string The processed input value
2523 */
2524 public function stdWrap_encapsLines($content = '', $conf = [])
2525 {
2526 return $this->encaps_lineSplit($content, $conf['encapsLines.']);
2527 }
2528
2529 /**
2530 * keywords
2531 * Transforms content into a CSV list to be used i.e. as keywords within a meta tag
2532 *
2533 * @param string $content Input value undergoing processing in this function.
2534 * @return string The processed input value
2535 */
2536 public function stdWrap_keywords($content = '')
2537 {
2538 return $this->keywords($content);
2539 }
2540
2541 /**
2542 * innerWrap
2543 * First of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2544 * See wrap
2545 *
2546 * @param string $content Input value undergoing processing in this function.
2547 * @param array $conf stdWrap properties for innerWrap.
2548 * @return string The processed input value
2549 */
2550 public function stdWrap_innerWrap($content = '', $conf = [])
2551 {
2552 return $this->wrap($content, $conf['innerWrap'] ?? null);
2553 }
2554
2555 /**
2556 * innerWrap2
2557 * Second of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2558 * See wrap
2559 *
2560 * @param string $content Input value undergoing processing in this function.
2561 * @param array $conf stdWrap properties for innerWrap2.
2562 * @return string The processed input value
2563 */
2564 public function stdWrap_innerWrap2($content = '', $conf = [])
2565 {
2566 return $this->wrap($content, $conf['innerWrap2'] ?? null);
2567 }
2568
2569 /**
2570 * addParams
2571 * Adds tag attributes to any content that is a tag
2572 *
2573 * @param string $content Input value undergoing processing in this function.
2574 * @param array $conf stdWrap properties for addParams.
2575 * @return string The processed input value
2576 * @deprecated since TYPO3 v9.5, will be removed in TYPO3 v10.0.
2577 */
2578 public function stdWrap_addParams($content = '', $conf = [])
2579 {
2580 return $this->addParams($content, $conf['addParams.'] ?? [], true);
2581 }
2582
2583 /**
2584 * filelink
2585 * Used to make lists of links to files
2586 * See wrap
2587 *
2588 * @param string $content Input value undergoing processing in this function.
2589 * @param array $conf stdWrap properties for filelink.
2590 * @return string The processed input value
2591 * @deprecated since TYPO3 v9.5, will be removed in TYPO3 v10.0. Use cObject FILES instead.
2592 */
2593 public function stdWrap_filelink($content = '', $conf = [])
2594 {
2595 return $this->filelink($content, $conf['filelink.'] ?? [], true);
2596 }
2597
2598 /**
2599 * preCObject
2600 * A content object that is prepended to the current content but between the innerWraps and the rest of the wraps
2601 *
2602 * @param string $content Input value undergoing processing in this function.
2603 * @param array $conf stdWrap properties for preCObject.
2604 * @return string The processed input value
2605 */
2606 public function stdWrap_preCObject($content = '', $conf = [])
2607 {
2608 return $this->cObjGetSingle($conf['preCObject'], $conf['preCObject.'], '/stdWrap/.preCObject') . $content;
2609 }
2610
2611 /**
2612 * postCObject
2613 * A content object that is appended to the current content but between the innerWraps and the rest of the wraps
2614 *
2615 * @param string $content Input value undergoing processing in this function.
2616 * @param array $conf stdWrap properties for postCObject.
2617 * @return string The processed input value
2618 */
2619 public function stdWrap_postCObject($content = '', $conf = [])
2620 {
2621 return $content . $this->cObjGetSingle($conf['postCObject'], $conf['postCObject.'], '/stdWrap/.postCObject');
2622 }
2623
2624 /**
2625 * wrapAlign
2626 * Wraps content with a div container having the style attribute text-align set to the given value
2627 * See wrap
2628 *
2629 * @param string $content Input value undergoing processing in this function.
2630 * @param array $conf stdWrap properties for wrapAlign.
2631 * @return string The processed input value
2632 */
2633 public function stdWrap_wrapAlign($content = '', $conf = [])
2634 {
2635 $wrapAlign = trim($conf['wrapAlign'] ?? '');
2636 if ($wrapAlign) {
2637 $content = $this->wrap($content, '<div style="text-align:' . htmlspecialchars($wrapAlign) . ';">|</div>');
2638 }
2639 return $content;
2640 }
2641
2642 /**
2643 * typolink
2644 * Wraps the content with a link tag
2645 * URLs and other attributes are created automatically by the values given in the stdWrap properties
2646 * See wrap
2647 *
2648 * @param string $content Input value undergoing processing in this function.
2649 * @param array $conf stdWrap properties for typolink.
2650 * @return string The processed input value
2651 */
2652 public function stdWrap_typolink($content = '', $conf = [])
2653 {
2654 return $this->typoLink($content, $conf['typolink.']);
2655 }
2656
2657 /**
2658 * wrap
2659 * This is the "mother" of all wraps
2660 * Third of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2661 * Basically it will put additional content before and after the current content using a split character as a placeholder for the current content
2662 * The default split character is | but it can be replaced with other characters by the property splitChar
2663 * Any other wrap that does not have own splitChar settings will be using the default split char though
2664 *
2665 * @param string $content Input value undergoing processing in this function.
2666 * @param array $conf stdWrap properties for wrap.
2667 * @return string The processed input value
2668 */
2669 public function stdWrap_wrap($content = '', $conf = [])
2670 {
2671 return $this->wrap(
2672 $content,
2673 $conf['wrap'] ?? null,
2674 $conf['wrap.']['splitChar'] ?? '|'
2675 );
2676 }
2677
2678 /**
2679 * noTrimWrap
2680 * Fourth of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2681 * The major difference to any other wrap is, that this one can make use of whitespace without trimming *
2682 *
2683 * @param string $content Input value undergoing processing in this function.
2684 * @param array $conf stdWrap properties for noTrimWrap.
2685 * @return string The processed input value
2686 */
2687 public function stdWrap_noTrimWrap($content = '', $conf = [])
2688 {
2689 $splitChar = isset($conf['noTrimWrap.']['splitChar.'])
2690 ? $this->stdWrap($conf['noTrimWrap.']['splitChar'] ?? '', $conf['noTrimWrap.']['splitChar.'])
2691 : $conf['noTrimWrap.']['splitChar'] ?? '';
2692 if ($splitChar === null || $splitChar === '') {
2693 $splitChar = '|';
2694 }
2695 $content = $this->noTrimWrap(
2696 $content,
2697 $conf['noTrimWrap'],
2698 $splitChar
2699 );
2700 return $content;
2701 }
2702
2703 /**
2704 * wrap2
2705 * Fifth of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2706 * The default split character is | but it can be replaced with other characters by the property splitChar
2707 *
2708 * @param string $content Input value undergoing processing in this function.
2709 * @param array $conf stdWrap properties for wrap2.
2710 * @return string The processed input value
2711 */
2712 public function stdWrap_wrap2($content = '', $conf = [])
2713 {
2714 return $this->wrap(
2715 $content,
2716 $conf['wrap2'] ?? null,
2717 $conf['wrap2.']['splitChar'] ?? '|'
2718 );
2719 }
2720
2721 /**
2722 * dataWrap
2723 * Sixth of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2724 * 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
2725 *
2726 * @param string $content Input value undergoing processing in this function.
2727 * @param array $conf stdWrap properties for dataWrap.
2728 * @return string The processed input value
2729 */
2730 public function stdWrap_dataWrap($content = '', $conf = [])
2731 {
2732 return $this->dataWrap($content, $conf['dataWrap']);
2733 }
2734
2735 /**
2736 * prepend
2737 * A content object that will be prepended to the current content after most of the wraps have already been applied
2738 *
2739 * @param string $content Input value undergoing processing in this function.
2740 * @param array $conf stdWrap properties for prepend.
2741 * @return string The processed input value
2742 */
2743 public function stdWrap_prepend($content = '', $conf = [])
2744 {
2745 return $this->cObjGetSingle($conf['prepend'], $conf['prepend.'], '/stdWrap/.prepend') . $content;
2746 }
2747
2748 /**
2749 * append
2750 * A content object that will be appended to the current content after most of the wraps have already been applied
2751 *
2752 * @param string $content Input value undergoing processing in this function.
2753 * @param array $conf stdWrap properties for append.
2754 * @return string The processed input value
2755 */
2756 public function stdWrap_append($content = '', $conf = [])
2757 {
2758 return $content . $this->cObjGetSingle($conf['append'], $conf['append.'], '/stdWrap/.append');
2759 }
2760
2761 /**
2762 * wrap3
2763 * Seventh of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2764 * The default split character is | but it can be replaced with other characters by the property splitChar
2765 *
2766 * @param string $content Input value undergoing processing in this function.
2767 * @param array $conf stdWrap properties for wrap3.
2768 * @return string The processed input value
2769 */
2770 public function stdWrap_wrap3($content = '', $conf = [])
2771 {
2772 return $this->wrap(
2773 $content,
2774 $conf['wrap3'] ?? null,
2775 $conf['wrap3.']['splitChar'] ?? '|'
2776 );
2777 }
2778
2779 /**
2780 * orderedStdWrap
2781 * Calls stdWrap for each entry in the provided array
2782 *
2783 * @param string $content Input value undergoing processing in this function.
2784 * @param array $conf stdWrap properties for orderedStdWrap.
2785 * @return string The processed input value
2786 */
2787 public function stdWrap_orderedStdWrap($content = '', $conf = [])
2788 {
2789 $sortedKeysArray = ArrayUtility::filterAndSortByNumericKeys($conf['orderedStdWrap.'], true);
2790 foreach ($sortedKeysArray as $key) {
2791 $content = $this->stdWrap($content, $conf['orderedStdWrap.'][$key . '.'] ?? null);
2792 }
2793 return $content;
2794 }
2795
2796 /**
2797 * outerWrap
2798 * Eighth of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2799 *
2800 * @param string $content Input value undergoing processing in this function.
2801 * @param array $conf stdWrap properties for outerWrap.
2802 * @return string The processed input value
2803 */
2804 public function stdWrap_outerWrap($content = '', $conf = [])
2805 {
2806 return $this->wrap($content, $conf['outerWrap'] ?? null);
2807 }
2808
2809 /**
2810 * insertData
2811 * Can fetch additional content the same way data does and replaces any occurrence of {field:whatever} with this content
2812 *
2813 * @param string $content Input value undergoing processing in this function.
2814 * @return string The processed input value
2815 */
2816 public function stdWrap_insertData($content = '')
2817 {
2818 return $this->insertData($content);
2819 }
2820
2821 /**
2822 * postUserFunc
2823 * Will execute a user function after the content has been modified by any other stdWrap function
2824 *
2825 * @param string $content Input value undergoing processing in this function.
2826 * @param array $conf stdWrap properties for postUserFunc.
2827 * @return string The processed input value
2828 */
2829 public function stdWrap_postUserFunc($content = '', $conf = [])
2830 {
2831 return $this->callUserFunction($conf['postUserFunc'], $conf['postUserFunc.'], $content);
2832 }
2833
2834 /**
2835 * postUserFuncInt
2836 * Will execute a user function after the content has been created and each time it is fetched from Cache
2837 * The result of this function itself will not be cached
2838 *
2839 * @param string $content Input value undergoing processing in this function.
2840 * @param array $conf stdWrap properties for postUserFuncInt.
2841 * @return string The processed input value
2842 */
2843 public function stdWrap_postUserFuncInt($content = '', $conf = [])
2844 {
2845 $substKey = 'INT_SCRIPT.' . $this->getTypoScriptFrontendController()->uniqueHash();
2846 $this->getTypoScriptFrontendController()->config['INTincScript'][$substKey] = [
2847 'content' => $content,
2848 'postUserFunc' => $conf['postUserFuncInt'],
2849 'conf' => $conf['postUserFuncInt.'],
2850 'type' => 'POSTUSERFUNC',
2851 'cObj' => serialize($this)
2852 ];
2853 $content = '<!--' . $substKey . '-->';
2854 return $content;
2855 }
2856
2857 /**
2858 * prefixComment
2859 * Will add HTML comments to the content to make it easier to identify certain content elements within the HTML output later on
2860 *
2861 * @param string $content Input value undergoing processing in this function.
2862 * @param array $conf stdWrap properties for prefixComment.
2863 * @return string The processed input value
2864 */
2865 public function stdWrap_prefixComment($content = '', $conf = [])
2866 {
2867 if (
2868 (!isset($this->getTypoScriptFrontendController()->config['config']['disablePrefixComment']) || !$this->getTypoScriptFrontendController()->config['config']['disablePrefixComment'])
2869 && !empty($conf['prefixComment'])
2870 ) {
2871 $content = $this->prefixComment($conf['prefixComment'], [], $content);
2872 }
2873 return $content;
2874 }
2875
2876 /**
2877 * editIcons
2878 * Will render icons for frontend editing as long as there is a BE user logged in
2879 *
2880 * @param string $content Input value undergoing processing in this function.
2881 * @param array $conf stdWrap properties for editIcons.
2882 * @return string The processed input value
2883 */
2884 public function stdWrap_editIcons($content = '', $conf = [])
2885 {
2886 if ($this->getTypoScriptFrontendController()->isBackendUserLoggedIn() && $conf['editIcons']) {
2887 if (!isset($conf['editIcons.']) || !is_array($conf['editIcons.'])) {
2888 $conf['editIcons.'] = [];
2889 }
2890 $content = $this->editIcons($content, $conf['editIcons'], $conf['editIcons.']);
2891 }
2892 return $content;
2893 }
2894
2895 /**
2896 * editPanel
2897 * Will render the edit panel for frontend editing as long as there is a BE user logged in
2898 *
2899 * @param string $content Input value undergoing processing in this function.
2900 * @param array $conf stdWrap properties for editPanel.
2901 * @return string The processed input value
2902 */
2903 public function stdWrap_editPanel($content = '', $conf = [])
2904 {
2905 if ($this->getTypoScriptFrontendController()->isBackendUserLoggedIn()) {
2906 $content = $this->editPanel($content, $conf['editPanel.']);
2907 }
2908 return $content;
2909 }
2910
2911 /**
2912 * Store content into cache
2913 *
2914 * @param string $content Input value undergoing processing in these functions.
2915 * @param array $conf All stdWrap properties, not just the ones for a particular function.
2916 * @return string The processed input value
2917 */
2918 public function stdWrap_cacheStore($content = '', $conf = [])
2919 {
2920 if (!isset($conf['cache.'])) {
2921 return $content;
2922 }
2923 $key = $this->calculateCacheKey($conf['cache.']);
2924 if (empty($key)) {
2925 return $content;
2926 }
2927 /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cacheFrontend */
2928 $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash');
2929 $tags = $this->calculateCacheTags($conf['cache.']);
2930 $lifetime = $this->calculateCacheLifetime($conf['cache.']);
2931 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap_cacheStore'] ?? [] as $_funcRef) {
2932 $params = [
2933 'key' => $key,
2934 'content' => $content,
2935 'lifetime' => $lifetime,
2936 'tags' => $tags
2937 ];
2938 GeneralUtility::callUserFunction($_funcRef, $params, $this);
2939 }
2940 $cacheFrontend->set($key, $content, $tags, $lifetime);
2941 return $content;
2942 }
2943
2944 /**
2945 * stdWrap post process hook
2946 * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
2947 * this hook executes functions at after the content has been modified by the rest of the stdWrap functions but still before debugging
2948 *
2949 * @param string $content Input value undergoing processing in these functions.
2950 * @param array $conf All stdWrap properties, not just the ones for a particular function.
2951 * @return string The processed input value
2952 */
2953 public function stdWrap_stdWrapPostProcess($content = '', $conf = [])
2954 {
2955 foreach ($this->stdWrapHookObjects as $hookObject) {
2956 /** @var ContentObjectStdWrapHookInterface $hookObject */
2957 $content = $hookObject->stdWrapPostProcess($content, $conf, $this);
2958 }
2959 return $content;
2960 }
2961
2962 /**
2963 * debug
2964 * Will output the content as readable HTML code
2965 *
2966 * @param string $content Input value undergoing processing in this function.
2967 * @return string The processed input value
2968 */
2969 public function stdWrap_debug($content = '')
2970 {
2971 return '<pre>' . htmlspecialchars($content) . '</pre>';
2972 }
2973
2974 /**
2975 * debugFunc
2976 * Will output the content in a debug table
2977 *
2978 * @param string $content Input value undergoing processing in this function.
2979 * @param array $conf stdWrap properties for debugFunc.
2980 * @return string The processed input value
2981 */
2982 public function stdWrap_debugFunc($content = '', $conf = [])
2983 {
2984 debug((int)$conf['debugFunc'] === 2 ? [$content] : $content);
2985 return $content;
2986 }
2987
2988 /**
2989 * debugData
2990 * Will output the data used by the current record in a debug table
2991 *
2992 * @param string $content Input value undergoing processing in this function.
2993 * @return string The processed input value
2994 */
2995 public function stdWrap_debugData($content = '')
2996 {