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