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