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