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