[TASK] Use a reference variable to pass $this into hooks
[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\Address;
23 use TYPO3\CMS\Core\Authentication\AbstractUserAuthentication;
24 use TYPO3\CMS\Core\Cache\CacheManager;
25 use TYPO3\CMS\Core\Context\Context;
26 use TYPO3\CMS\Core\Context\LanguageAspect;
27 use TYPO3\CMS\Core\Core\Environment;
28 use TYPO3\CMS\Core\Database\Connection;
29 use TYPO3\CMS\Core\Database\ConnectionPool;
30 use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
31 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
32 use TYPO3\CMS\Core\Database\Query\QueryHelper;
33 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
34 use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
35 use TYPO3\CMS\Core\Domain\Repository\PageRepository;
36 use TYPO3\CMS\Core\Html\HtmlParser;
37 use TYPO3\CMS\Core\Imaging\ImageManipulation\Area;
38 use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
39 use TYPO3\CMS\Core\LinkHandling\LinkService;
40 use TYPO3\CMS\Core\Log\LogManager;
41 use TYPO3\CMS\Core\Mail\MailMessage;
42 use TYPO3\CMS\Core\Resource\Exception;
43 use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
44 use TYPO3\CMS\Core\Resource\File;
45 use TYPO3\CMS\Core\Resource\FileInterface;
46 use TYPO3\CMS\Core\Resource\FileReference;
47 use TYPO3\CMS\Core\Resource\Folder;
48 use TYPO3\CMS\Core\Resource\ProcessedFile;
49 use TYPO3\CMS\Core\Resource\ResourceFactory;
50 use TYPO3\CMS\Core\Service\DependencyOrderingService;
51 use TYPO3\CMS\Core\Service\FlexFormService;
52 use TYPO3\CMS\Core\Service\MarkerBasedTemplateService;
53 use TYPO3\CMS\Core\Site\SiteFinder;
54 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
55 use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
56 use TYPO3\CMS\Core\TypoScript\TypoScriptService;
57 use TYPO3\CMS\Core\Utility\ArrayUtility;
58 use TYPO3\CMS\Core\Utility\DebugUtility;
59 use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException;
60 use TYPO3\CMS\Core\Utility\GeneralUtility;
61 use TYPO3\CMS\Core\Utility\HttpUtility;
62 use TYPO3\CMS\Core\Utility\MailUtility;
63 use TYPO3\CMS\Core\Utility\MathUtility;
64 use TYPO3\CMS\Core\Utility\StringUtility;
65 use TYPO3\CMS\Core\Versioning\VersionState;
66 use TYPO3\CMS\Frontend\ContentObject\Exception\ContentRenderingException;
67 use TYPO3\CMS\Frontend\ContentObject\Exception\ExceptionHandlerInterface;
68 use TYPO3\CMS\Frontend\ContentObject\Exception\ProductionExceptionHandler;
69 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
70 use TYPO3\CMS\Frontend\Http\UrlProcessorInterface;
71 use TYPO3\CMS\Frontend\Imaging\GifBuilder;
72 use TYPO3\CMS\Frontend\Page\PageLayoutResolver;
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 [$objectType, $identifier] = explode(':', $this->currentFile, 2);
503 try {
504 if ($objectType === 'File') {
505 $this->currentFile = GeneralUtility::makeInstance(ResourceFactory::class)->retrieveFileOrFolderObject($identifier);
506 } elseif ($objectType === 'FileReference') {
507 $this->currentFile = GeneralUtility::makeInstance(ResourceFactory::class)->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 [$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 (Environment::getContext()->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 = GeneralUtility::makeInstance(ResourceFactory::class)->getFileObject((int)$imageFile);
1298 } else {
1299 $file = GeneralUtility::makeInstance(ResourceFactory::class)->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.']['directImageLink'] = (bool)$conf['directImageLink'];
1378 $conf['linkParams.']['parameter'] = $url;
1379 $string = $this->typoLink($string, $conf['linkParams.']);
1380 }
1381 if (isset($conf['stdWrap.'])) {
1382 $string = $this->stdWrap($string, $conf['stdWrap.']);
1383 }
1384 $content = $a1 . $string . $a2;
1385 }
1386 return $content;
1387 }
1388
1389 /**
1390 * Sets the SYS_LASTCHANGED timestamp if input timestamp is larger than current value.
1391 * The SYS_LASTCHANGED timestamp can be used by various caching/indexing applications to determine if the page has new content.
1392 * Therefore you should call this function with the last-changed timestamp of any element you display.
1393 *
1394 * @param int $tstamp Unix timestamp (number of seconds since 1970)
1395 * @see TypoScriptFrontendController::setSysLastChanged()
1396 */
1397 public function lastChanged($tstamp)
1398 {
1399 $tstamp = (int)$tstamp;
1400 $tsfe = $this->getTypoScriptFrontendController();
1401 if ($tstamp > (int)$tsfe->register['SYS_LASTCHANGED']) {
1402 $tsfe->register['SYS_LASTCHANGED'] = $tstamp;
1403 }
1404 }
1405
1406 /**
1407 * Wraps the input string by the $wrap value and implements the "linkWrap" data type as well.
1408 * 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.
1409 *
1410 * @param string $content Input string
1411 * @param string $wrap A string where the first two parts separated by "|" (vertical line) will be wrapped around the input string
1412 * @return string Wrapped output string
1413 * @see wrap()
1414 * @see cImage()
1415 */
1416 public function linkWrap($content, $wrap)
1417 {
1418 $wrapArr = explode('|', $wrap);
1419 if (preg_match('/\\{([0-9]*)\\}/', $wrapArr[0], $reg)) {
1420 $uid = $this->getTypoScriptFrontendController()->tmpl->rootLine[$reg[1]]['uid'] ?? null;
1421 if ($uid) {
1422 $wrapArr[0] = str_replace($reg[0], $uid, $wrapArr[0]);
1423 }
1424 }
1425 return trim($wrapArr[0] ?? '') . $content . trim($wrapArr[1] ?? '');
1426 }
1427
1428 /**
1429 * An abstraction method which creates an alt or title parameter for an HTML img, applet, area or input element and the FILE content element.
1430 * From the $conf array it implements the properties "altText", "titleText" and "longdescURL"
1431 *
1432 * @param array $conf TypoScript configuration properties
1433 * @param bool $longDesc If set, the longdesc attribute will be generated - must only be used for img elements!
1434 * @return string Parameter string containing alt and title parameters (if any)
1435 * @see cImage()
1436 */
1437 public function getAltParam($conf, $longDesc = true)
1438 {
1439 $altText = isset($conf['altText.']) ? trim($this->stdWrap($conf['altText'], $conf['altText.'])) : trim($conf['altText']);
1440 $titleText = isset($conf['titleText.']) ? trim($this->stdWrap($conf['titleText'], $conf['titleText.'])) : trim($conf['titleText']);
1441 if (isset($conf['longdescURL.']) && $this->getTypoScriptFrontendController()->config['config']['doctype'] !== 'html5') {
1442 $longDescUrl = $this->typoLink_URL($conf['longdescURL.']);
1443 } else {
1444 $longDescUrl = trim($conf['longdescURL']);
1445 }
1446 $longDescUrl = strip_tags($longDescUrl);
1447
1448 // "alt":
1449 $altParam = ' alt="' . htmlspecialchars($altText) . '"';
1450 // "title":
1451 $emptyTitleHandling = isset($conf['emptyTitleHandling.']) ? $this->stdWrap($conf['emptyTitleHandling'], $conf['emptyTitleHandling.']) : $conf['emptyTitleHandling'];
1452 // Choices: 'keepEmpty' | 'useAlt' | 'removeAttr'
1453 if ($titleText || $emptyTitleHandling === 'keepEmpty') {
1454 $altParam .= ' title="' . htmlspecialchars($titleText) . '"';
1455 } elseif (!$titleText && $emptyTitleHandling === 'useAlt') {
1456 $altParam .= ' title="' . htmlspecialchars($altText) . '"';
1457 }
1458 // "longDesc" URL
1459 if ($longDesc && !empty($longDescUrl)) {
1460 $altParam .= ' longdesc="' . htmlspecialchars($longDescUrl) . '"';
1461 }
1462 return $altParam;
1463 }
1464
1465 /**
1466 * An abstraction method to add parameters to an A tag.
1467 * Uses the ATagParams property.
1468 *
1469 * @param array $conf TypoScript configuration properties
1470 * @param bool|int $addGlobal If set, will add the global config.ATagParams to the link
1471 * @return string String containing the parameters to the A tag (if non empty, with a leading space)
1472 * @see typolink()
1473 */
1474 public function getATagParams($conf, $addGlobal = 1)
1475 {
1476 $aTagParams = '';
1477 if ($conf['ATagParams.'] ?? false) {
1478 $aTagParams = ' ' . $this->stdWrap($conf['ATagParams'], $conf['ATagParams.']);
1479 } elseif ($conf['ATagParams'] ?? false) {
1480 $aTagParams = ' ' . $conf['ATagParams'];
1481 }
1482 if ($addGlobal) {
1483 $aTagParams = ' ' . trim($this->getTypoScriptFrontendController()->ATagParams . $aTagParams);
1484 }
1485 // Extend params
1486 $_params = [
1487 'conf' => &$conf,
1488 'aTagParams' => &$aTagParams
1489 ];
1490 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getATagParamsPostProc'] ?? [] as $className) {
1491 $processor = & GeneralUtility::makeInstance($className);
1492 $aTagParams = $processor->process($_params, $this);
1493 }
1494
1495 $aTagParams = trim($aTagParams);
1496 if (!empty($aTagParams)) {
1497 $aTagParams = ' ' . $aTagParams;
1498 }
1499
1500 return $aTagParams;
1501 }
1502
1503 /**
1504 * All extension links should ask this function for additional properties to their tags.
1505 * Designed to add for instance an "onclick" property for site tracking systems.
1506 *
1507 * @param string $URL URL of the website
1508 * @param string $TYPE
1509 * @return string The additional tag properties
1510 */
1511 public function extLinkATagParams($URL, $TYPE)
1512 {
1513 $out = '';
1514 if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['extLinkATagParamsHandler'])) {
1515 $extLinkATagParamsHandler = GeneralUtility::makeInstance($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['extLinkATagParamsHandler']);
1516 if (method_exists($extLinkATagParamsHandler, 'main')) {
1517 $out .= trim($extLinkATagParamsHandler->main($URL, $TYPE, $this));
1518 }
1519 }
1520 return trim($out) ? ' ' . trim($out) : '';
1521 }
1522
1523 /***********************************************
1524 *
1525 * HTML template processing functions
1526 *
1527 ***********************************************/
1528
1529 /**
1530 * Sets the current file object during iterations over files.
1531 *
1532 * @param File $fileObject The file object.
1533 */
1534 public function setCurrentFile($fileObject)
1535 {
1536 $this->currentFile = $fileObject;
1537 }
1538
1539 /**
1540 * Gets the current file object during iterations over files.
1541 *
1542 * @return File The current file object.
1543 */
1544 public function getCurrentFile()
1545 {
1546 return $this->currentFile;
1547 }
1548
1549 /***********************************************
1550 *
1551 * "stdWrap" + sub functions
1552 *
1553 ***********************************************/
1554 /**
1555 * The "stdWrap" function. This is the implementation of what is known as "stdWrap properties" in TypoScript.
1556 * Basically "stdWrap" performs some processing of a value based on properties in the input $conf array(holding the TypoScript "stdWrap properties")
1557 * 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.
1558 *
1559 * If $this->alternativeData is an array it's used instead of the $this->data array in ->getData
1560 *
1561 * @param string $content Input value undergoing processing in this function. Possibly substituted by other values fetched from another source.
1562 * @param array $conf TypoScript "stdWrap properties".
1563 * @return string The processed input value
1564 */
1565 public function stdWrap($content = '', $conf = [])
1566 {
1567 $content = (string)$content;
1568 // If there is any hook object, activate all of the process and override functions.
1569 // The hook interface ContentObjectStdWrapHookInterface takes care that all 4 methods exist.
1570 if ($this->stdWrapHookObjects) {
1571 $conf['stdWrapPreProcess'] = 1;
1572 $conf['stdWrapOverride'] = 1;
1573 $conf['stdWrapProcess'] = 1;
1574 $conf['stdWrapPostProcess'] = 1;
1575 }
1576
1577 if (!is_array($conf) || !$conf) {
1578 return $content;
1579 }
1580
1581 // Cache handling
1582 if (isset($conf['cache.']) && is_array($conf['cache.'])) {
1583 $conf['cache.']['key'] = $this->stdWrap($conf['cache.']['key'], $conf['cache.']['key.']);
1584 $conf['cache.']['tags'] = $this->stdWrap($conf['cache.']['tags'], $conf['cache.']['tags.']);
1585 $conf['cache.']['lifetime'] = $this->stdWrap($conf['cache.']['lifetime'], $conf['cache.']['lifetime.']);
1586 $conf['cacheRead'] = 1;
1587 $conf['cacheStore'] = 1;
1588 }
1589 // The configuration is sorted and filtered by intersection with the defined stdWrapOrder.
1590 $sortedConf = array_keys(array_intersect_key($this->stdWrapOrder, $conf));
1591 // Functions types that should not make use of nested stdWrap function calls to avoid conflicts with internal TypoScript used by these functions
1592 $stdWrapDisabledFunctionTypes = 'cObject,functionName,stdWrap';
1593 // Additional Array to check whether a function has already been executed
1594 $isExecuted = [];
1595 // Additional switch to make sure 'required', 'if' and 'fieldRequired'
1596 // will still stop rendering immediately in case they return FALSE
1597 $this->stdWrapRecursionLevel++;
1598 $this->stopRendering[$this->stdWrapRecursionLevel] = false;
1599 // execute each function in the predefined order
1600 foreach ($sortedConf as $stdWrapName) {
1601 // eliminate the second key of a pair 'key'|'key.' to make sure functions get called only once and check if rendering has been stopped
1602 if ((!isset($isExecuted[$stdWrapName]) || !$isExecuted[$stdWrapName]) && !$this->stopRendering[$this->stdWrapRecursionLevel]) {
1603 $functionName = rtrim($stdWrapName, '.');
1604 $functionProperties = $functionName . '.';
1605 $functionType = $this->stdWrapOrder[$functionName] ?? null;
1606 // If there is any code on the next level, check if it contains "official" stdWrap functions
1607 // if yes, execute them first - will make each function stdWrap aware
1608 // so additional stdWrap calls within the functions can be removed, since the result will be the same
1609 if (!empty($conf[$functionProperties]) && !GeneralUtility::inList($stdWrapDisabledFunctionTypes, $functionType)) {
1610 if (array_intersect_key($this->stdWrapOrder, $conf[$functionProperties])) {
1611 // Check if there's already content available before processing
1612 // any ifEmpty or ifBlank stdWrap properties
1613 if (($functionName === 'ifEmpty' && !empty($content)) ||
1614 ($functionName === 'ifBlank' && $content !== '')) {
1615 continue;
1616 }
1617
1618 $conf[$functionName] = $this->stdWrap($conf[$functionName] ?? '', $conf[$functionProperties] ?? []);
1619 }
1620 }
1621 // Check if key is still containing something, since it might have been changed by next level stdWrap before
1622 if ((isset($conf[$functionName]) || $conf[$functionProperties])
1623 && ($functionType !== 'boolean' || $conf[$functionName])
1624 ) {
1625 // Get just that part of $conf that is needed for the particular function
1626 $singleConf = [
1627 $functionName => $conf[$functionName] ?? null,
1628 $functionProperties => $conf[$functionProperties] ?? null
1629 ];
1630 // Hand over the whole $conf array to the stdWrapHookObjects
1631 if ($functionType === 'hook') {
1632 $singleConf = $conf;
1633 }
1634 // Add both keys - with and without the dot - to the set of executed functions
1635 $isExecuted[$functionName] = true;
1636 $isExecuted[$functionProperties] = true;
1637 // Call the function with the prefix stdWrap_ to make sure nobody can execute functions just by adding their name to the TS Array
1638 $functionName = 'stdWrap_' . $functionName;
1639 $content = $this->{$functionName}($content, $singleConf);
1640 } elseif ($functionType === 'boolean' && !$conf[$functionName]) {
1641 $isExecuted[$functionName] = true;
1642 $isExecuted[$functionProperties] = true;
1643 }
1644 }
1645 }
1646 unset($this->stopRendering[$this->stdWrapRecursionLevel]);
1647 $this->stdWrapRecursionLevel--;
1648
1649 return $content;
1650 }
1651
1652 /**
1653 * Gets a configuration value by passing them through stdWrap first and taking a default value if stdWrap doesn't yield a result.
1654 *
1655 * @param string $key The config variable key (from TS array).
1656 * @param array $config The TypoScript array.
1657 * @param string $defaultValue Optional default value.
1658 * @return string Value of the config variable
1659 */
1660 public function stdWrapValue($key, array $config, $defaultValue = '')
1661 {
1662 if (isset($config[$key])) {
1663 if (!isset($config[$key . '.'])) {
1664 return $config[$key];
1665 }
1666 } elseif (isset($config[$key . '.'])) {
1667 $config[$key] = '';
1668 } else {
1669 return $defaultValue;
1670 }
1671 $stdWrapped = $this->stdWrap($config[$key], $config[$key . '.']);
1672 return $stdWrapped ?: $defaultValue;
1673 }
1674
1675 /**
1676 * stdWrap pre process hook
1677 * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
1678 * this hook will execute functions before any other stdWrap function can modify anything
1679 *
1680 * @param string $content Input value undergoing processing in these functions.
1681 * @param array $conf All stdWrap properties, not just the ones for a particular function.
1682 * @return string The processed input value
1683 */
1684 public function stdWrap_stdWrapPreProcess($content = '', $conf = [])
1685 {
1686 foreach ($this->stdWrapHookObjects as $hookObject) {
1687 /** @var ContentObjectStdWrapHookInterface $hookObject */
1688 $content = $hookObject->stdWrapPreProcess($content, $conf, $this);
1689 }
1690 return $content;
1691 }
1692
1693 /**
1694 * Check if content was cached before (depending on the given cache key)
1695 *
1696 * @param string $content Input value undergoing processing in these functions.
1697 * @param array $conf All stdWrap properties, not just the ones for a particular function.
1698 * @return string The processed input value
1699 */
1700 public function stdWrap_cacheRead($content = '', $conf = [])
1701 {
1702 if (!isset($conf['cache.'])) {
1703 return $content;
1704 }
1705 $result = $this->getFromCache($conf['cache.']);
1706 return $result === false ? $content : $result;
1707 }
1708
1709 /**
1710 * Add tags to page cache (comma-separated list)
1711 *
1712 * @param string $content Input value undergoing processing in these functions.
1713 * @param array $conf All stdWrap properties, not just the ones for a particular function.
1714 * @return string The processed input value
1715 */
1716 public function stdWrap_addPageCacheTags($content = '', $conf = [])
1717 {
1718 $tags = isset($conf['addPageCacheTags.'])
1719 ? $this->stdWrap($conf['addPageCacheTags'], $conf['addPageCacheTags.'])
1720 : $conf['addPageCacheTags'];
1721 if (!empty($tags)) {
1722 $cacheTags = GeneralUtility::trimExplode(',', $tags, true);
1723 $this->getTypoScriptFrontendController()->addCacheTags($cacheTags);
1724 }
1725 return $content;
1726 }
1727
1728 /**
1729 * setContentToCurrent
1730 * actually it just does the contrary: Sets the value of 'current' based on current content
1731 *
1732 * @param string $content Input value undergoing processing in this function.
1733 * @return string The processed input value
1734 */
1735 public function stdWrap_setContentToCurrent($content = '')
1736 {
1737 $this->data[$this->currentValKey] = $content;
1738 return $content;
1739 }
1740
1741 /**
1742 * setCurrent
1743 * Sets the value of 'current' based on the outcome of stdWrap operations
1744 *
1745 * @param string $content Input value undergoing processing in this function.
1746 * @param array $conf stdWrap properties for setCurrent.
1747 * @return string The processed input value
1748 */
1749 public function stdWrap_setCurrent($content = '', $conf = [])
1750 {
1751 $this->data[$this->currentValKey] = $conf['setCurrent'] ?? null;
1752 return $content;
1753 }
1754
1755 /**
1756 * lang
1757 * Translates content based on the language currently used by the FE
1758 *
1759 * @param string $content Input value undergoing processing in this function.
1760 * @param array $conf stdWrap properties for lang.
1761 * @return string The processed input value
1762 */
1763 public function stdWrap_lang($content = '', $conf = [])
1764 {
1765 $currentLanguageCode = $this->getTypoScriptFrontendController()->getLanguage()->getTypo3Language();
1766 if ($currentLanguageCode && isset($conf['lang.'][$currentLanguageCode])) {
1767 $content = $conf['lang.'][$currentLanguageCode];
1768 }
1769 return $content;
1770 }
1771
1772 /**
1773 * data
1774 * Gets content from different sources based on getText functions, makes use of alternativeData, when set
1775 *
1776 * @param string $content Input value undergoing processing in this function.
1777 * @param array $conf stdWrap properties for data.
1778 * @return string The processed input value
1779 */
1780 public function stdWrap_data($content = '', $conf = [])
1781 {
1782 $content = $this->getData($conf['data'], is_array($this->alternativeData) ? $this->alternativeData : $this->data);
1783 // This must be unset directly after
1784 $this->alternativeData = '';
1785 return $content;
1786 }
1787
1788 /**
1789 * field
1790 * Gets content from a DB field
1791 *
1792 * @param string $content Input value undergoing processing in this function.
1793 * @param array $conf stdWrap properties for field.
1794 * @return string The processed input value
1795 */
1796 public function stdWrap_field($content = '', $conf = [])
1797 {
1798 return $this->getFieldVal($conf['field']);
1799 }
1800
1801 /**
1802 * current
1803 * Gets content that has been previously set as 'current'
1804 * Can be set via setContentToCurrent or setCurrent or will be set automatically i.e. inside the split function
1805 *
1806 * @param string $content Input value undergoing processing in this function.
1807 * @param array $conf stdWrap properties for current.
1808 * @return string The processed input value
1809 */
1810 public function stdWrap_current($content = '', $conf = [])
1811 {
1812 return $this->data[$this->currentValKey];
1813 }
1814
1815 /**
1816 * cObject
1817 * Will replace the content with the value of an official TypoScript cObject
1818 * like TEXT, COA, HMENU
1819 *
1820 * @param string $content Input value undergoing processing in this function.
1821 * @param array $conf stdWrap properties for cObject.
1822 * @return string The processed input value
1823 */
1824 public function stdWrap_cObject($content = '', $conf = [])
1825 {
1826 return $this->cObjGetSingle($conf['cObject'] ?? '', $conf['cObject.'] ?? [], '/stdWrap/.cObject');
1827 }
1828
1829 /**
1830 * numRows
1831 * Counts the number of returned records of a DB operation
1832 * makes use of select internally
1833 *
1834 * @param string $content Input value undergoing processing in this function.
1835 * @param array $conf stdWrap properties for numRows.
1836 * @return string The processed input value
1837 */
1838 public function stdWrap_numRows($content = '', $conf = [])
1839 {
1840 return $this->numRows($conf['numRows.']);
1841 }
1842
1843 /**
1844 * preUserFunc
1845 * Will execute a user public function before the content will be modified by any other stdWrap function
1846 *
1847 * @param string $content Input value undergoing processing in this function.
1848 * @param array $conf stdWrap properties for preUserFunc.
1849 * @return string The processed input value
1850 */
1851 public function stdWrap_preUserFunc($content = '', $conf = [])
1852 {
1853 return $this->callUserFunction($conf['preUserFunc'], $conf['preUserFunc.'], $content);
1854 }
1855
1856 /**
1857 * stdWrap override hook
1858 * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
1859 * this hook will execute functions on existing content but still before the content gets modified or replaced
1860 *
1861 * @param string $content Input value undergoing processing in these functions.
1862 * @param array $conf All stdWrap properties, not just the ones for a particular function.
1863 * @return string The processed input value
1864 */
1865 public function stdWrap_stdWrapOverride($content = '', $conf = [])
1866 {
1867 foreach ($this->stdWrapHookObjects as $hookObject) {
1868 /** @var ContentObjectStdWrapHookInterface $hookObject */
1869 $content = $hookObject->stdWrapOverride($content, $conf, $this);
1870 }
1871 return $content;
1872 }
1873
1874 /**
1875 * override
1876 * Will override the current value of content with its own value'
1877 *
1878 * @param string $content Input value undergoing processing in this function.
1879 * @param array $conf stdWrap properties for override.
1880 * @return string The processed input value
1881 */
1882 public function stdWrap_override($content = '', $conf = [])
1883 {
1884 if (trim($conf['override'] ?? false)) {
1885 $content = $conf['override'];
1886 }
1887 return $content;
1888 }
1889
1890 /**
1891 * preIfEmptyListNum
1892 * Gets a value off a CSV list before the following ifEmpty check
1893 * 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
1894 *
1895 * @param string $content Input value undergoing processing in this function.
1896 * @param array $conf stdWrap properties for preIfEmptyListNum.
1897 * @return string The processed input value
1898 */
1899 public function stdWrap_preIfEmptyListNum($content = '', $conf = [])
1900 {
1901 return $this->listNum($content, $conf['preIfEmptyListNum'] ?? null, $conf['preIfEmptyListNum.']['splitChar'] ?? null);
1902 }
1903
1904 /**
1905 * ifNull
1906 * Will set content to a replacement value in case the value of content is NULL
1907 *
1908 * @param string|null $content Input value undergoing processing in this function.
1909 * @param array $conf stdWrap properties for ifNull.
1910 * @return string The processed input value
1911 */
1912 public function stdWrap_ifNull($content = '', $conf = [])
1913 {
1914 return $content ?? $conf['ifNull'];
1915 }
1916
1917 /**
1918 * ifEmpty
1919 * Will set content to a replacement value in case the trimmed value of content returns FALSE
1920 * 0 (zero) will be replaced as well
1921 *
1922 * @param string $content Input value undergoing processing in this function.
1923 * @param array $conf stdWrap properties for ifEmpty.
1924 * @return string The processed input value
1925 */
1926 public function stdWrap_ifEmpty($content = '', $conf = [])
1927 {
1928 if (!trim($content)) {
1929 $content = $conf['ifEmpty'];
1930 }
1931 return $content;
1932 }
1933
1934 /**
1935 * ifBlank
1936 * Will set content to a replacement value in case the trimmed value of content has no length
1937 * 0 (zero) will not be replaced
1938 *
1939 * @param string $content Input value undergoing processing in this function.
1940 * @param array $conf stdWrap properties for ifBlank.
1941 * @return string The processed input value
1942 */
1943 public function stdWrap_ifBlank($content = '', $conf = [])
1944 {
1945 if (trim($content) === '') {
1946 $content = $conf['ifBlank'];
1947 }
1948 return $content;
1949 }
1950
1951 /**
1952 * listNum
1953 * Gets a value off a CSV list after ifEmpty check
1954 * Might return an empty value in case the CSV does not contain a value at the position given by listNum
1955 * Use preIfEmptyListNum to avoid that behaviour
1956 *
1957 * @param string $content Input value undergoing processing in this function.
1958 * @param array $conf stdWrap properties for listNum.
1959 * @return string The processed input value
1960 */
1961 public function stdWrap_listNum($content = '', $conf = [])
1962 {
1963 return $this->listNum($content, $conf['listNum'] ?? null, $conf['listNum.']['splitChar'] ?? null);
1964 }
1965
1966 /**
1967 * trim
1968 * Cuts off any whitespace at the beginning and the end of the content
1969 *
1970 * @param string $content Input value undergoing processing in this function.
1971 * @return string The processed input value
1972 */
1973 public function stdWrap_trim($content = '')
1974 {
1975 return trim($content);
1976 }
1977
1978 /**
1979 * strPad
1980 * Will return a string padded left/right/on both sides, based on configuration given as stdWrap properties
1981 *
1982 * @param string $content Input value undergoing processing in this function.
1983 * @param array $conf stdWrap properties for strPad.
1984 * @return string The processed input value
1985 */
1986 public function stdWrap_strPad($content = '', $conf = [])
1987 {
1988 // Must specify a length in conf for this to make sense
1989 $length = 0;
1990 // Padding with space is PHP-default
1991 $padWith = ' ';
1992 // Padding on the right side is PHP-default
1993 $padType = STR_PAD_RIGHT;
1994 if (!empty($conf['strPad.']['length'])) {
1995 $length = isset($conf['strPad.']['length.']) ? $this->stdWrap($conf['strPad.']['length'], $conf['strPad.']['length.']) : $conf['strPad.']['length'];
1996 $length = (int)$length;
1997 }
1998 if (isset($conf['strPad.']['padWith']) && (string)$conf['strPad.']['padWith'] !== '') {
1999 $padWith = isset($conf['strPad.']['padWith.']) ? $this->stdWrap($conf['strPad.']['padWith'], $conf['strPad.']['padWith.']) : $conf['strPad.']['padWith'];
2000 }
2001 if (!empty($conf['strPad.']['type'])) {
2002 $type = isset($conf['strPad.']['type.']) ? $this->stdWrap($conf['strPad.']['type'], $conf['strPad.']['type.']) : $conf['strPad.']['type'];
2003 if (strtolower($type) === 'left') {
2004 $padType = STR_PAD_LEFT;
2005 } elseif (strtolower($type) === 'both') {
2006 $padType = STR_PAD_BOTH;
2007 }
2008 }
2009 return str_pad($content, $length, $padWith, $padType);
2010 }
2011
2012 /**
2013 * stdWrap
2014 * A recursive call of the stdWrap function set
2015 * This enables the user to execute stdWrap functions in another than the predefined order
2016 * It modifies the content, not the property
2017 * while the new feature of chained stdWrap functions modifies the property and not the content
2018 *
2019 * @param string $content Input value undergoing processing in this function.
2020 * @param array $conf stdWrap properties for stdWrap.
2021 * @return string The processed input value
2022 */
2023 public function stdWrap_stdWrap($content = '', $conf = [])
2024 {
2025 return $this->stdWrap($content, $conf['stdWrap.']);
2026 }
2027
2028 /**
2029 * stdWrap process hook
2030 * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
2031 * this hook executes functions directly after the recursive stdWrap function call but still before the content gets modified
2032 *
2033 * @param string $content Input value undergoing processing in these functions.
2034 * @param array $conf All stdWrap properties, not just the ones for a particular function.
2035 * @return string The processed input value
2036 */
2037 public function stdWrap_stdWrapProcess($content = '', $conf = [])
2038 {
2039 foreach ($this->stdWrapHookObjects as $hookObject) {
2040 /** @var ContentObjectStdWrapHookInterface $hookObject */
2041 $content = $hookObject->stdWrapProcess($content, $conf, $this);
2042 }
2043 return $content;
2044 }
2045
2046 /**
2047 * required
2048 * Will immediately stop rendering and return an empty value
2049 * when there is no content at this point
2050 *
2051 * @param string $content Input value undergoing processing in this function.
2052 * @return string The processed input value
2053 */
2054 public function stdWrap_required($content = '')
2055 {
2056 if ((string)$content === '') {
2057 $content = '';
2058 $this->stopRendering[$this->stdWrapRecursionLevel] = true;
2059 }
2060 return $content;
2061 }
2062
2063 /**
2064 * if
2065 * Will immediately stop rendering and return an empty value
2066 * when the result of the checks returns FALSE
2067 *
2068 * @param string $content Input value undergoing processing in this function.
2069 * @param array $conf stdWrap properties for if.
2070 * @return string The processed input value
2071 */
2072 public function stdWrap_if($content = '', $conf = [])
2073 {
2074 if (empty($conf['if.']) || $this->checkIf($conf['if.'])) {
2075 return $content;
2076 }
2077 $this->stopRendering[$this->stdWrapRecursionLevel] = true;
2078 return '';
2079 }
2080
2081 /**
2082 * fieldRequired
2083 * Will immediately stop rendering and return an empty value
2084 * when there is no content in the field given by fieldRequired
2085 *
2086 * @param string $content Input value undergoing processing in this function.
2087 * @param array $conf stdWrap properties for fieldRequired.
2088 * @return string The processed input value
2089 */
2090 public function stdWrap_fieldRequired($content = '', $conf = [])
2091 {
2092 if (!trim($this->data[$conf['fieldRequired'] ?? null] ?? '')) {
2093 $content = '';
2094 $this->stopRendering[$this->stdWrapRecursionLevel] = true;
2095 }
2096 return $content;
2097 }
2098
2099 /**
2100 * stdWrap csConv: Converts the input to UTF-8
2101 *
2102 * The character set of the input must be specified. Returns the input if
2103 * matters go wrong, for example if an invalid character set is given.
2104 *
2105 * @param string $content The string to convert.
2106 * @param array $conf stdWrap properties for csConv.
2107 * @return string The processed input.
2108 */
2109 public function stdWrap_csConv($content = '', $conf = [])
2110 {
2111 if (!empty($conf['csConv'])) {
2112 $output = mb_convert_encoding($content, 'utf-8', trim(strtolower($conf['csConv'])));
2113 return $output !== false && $output !== '' ? $output : $content;
2114 }
2115 return $content;
2116 }
2117
2118 /**
2119 * parseFunc
2120 * Will parse the content based on functions given as stdWrap properties
2121 * Heavily used together with RTE based content
2122 *
2123 * @param string $content Input value undergoing processing in this function.
2124 * @param array $conf stdWrap properties for parseFunc.
2125 * @return string The processed input value
2126 */
2127 public function stdWrap_parseFunc($content = '', $conf = [])
2128 {
2129 return $this->parseFunc($content, $conf['parseFunc.'], $conf['parseFunc']);
2130 }
2131
2132 /**
2133 * HTMLparser
2134 * Will parse HTML content based on functions given as stdWrap properties
2135 * Heavily used together with RTE based content
2136 *
2137 * @param string $content Input value undergoing processing in this function.
2138 * @param array $conf stdWrap properties for HTMLparser.
2139 * @return string The processed input value
2140 */
2141 public function stdWrap_HTMLparser($content = '', $conf = [])
2142 {
2143 if (isset($conf['HTMLparser.']) && is_array($conf['HTMLparser.'])) {
2144 $content = $this->HTMLparser_TSbridge($content, $conf['HTMLparser.']);
2145 }
2146 return $content;
2147 }
2148
2149 /**
2150 * split
2151 * Will split the content by a given token and treat the results separately
2152 * Automatically fills 'current' with a single result
2153 *
2154 * @param string $content Input value undergoing processing in this function.
2155 * @param array $conf stdWrap properties for split.
2156 * @return string The processed input value
2157 */
2158 public function stdWrap_split($content = '', $conf = [])
2159 {
2160 return $this->splitObj($content, $conf['split.']);
2161 }
2162
2163 /**
2164 * replacement
2165 * Will execute replacements on the content (optionally with preg-regex)
2166 *
2167 * @param string $content Input value undergoing processing in this function.
2168 * @param array $conf stdWrap properties for replacement.
2169 * @return string The processed input value
2170 */
2171 public function stdWrap_replacement($content = '', $conf = [])
2172 {
2173 return $this->replacement($content, $conf['replacement.']);
2174 }
2175
2176 /**
2177 * prioriCalc
2178 * Will use the content as a mathematical term and calculate the result
2179 * Can be set to 1 to just get a calculated value or 'intval' to get the integer of the result
2180 *
2181 * @param string $content Input value undergoing processing in this function.
2182 * @param array $conf stdWrap properties for prioriCalc.
2183 * @return string The processed input value
2184 */
2185 public function stdWrap_prioriCalc($content = '', $conf = [])
2186 {
2187 $content = MathUtility::calculateWithParentheses($content);
2188 if (!empty($conf['prioriCalc']) && $conf['prioriCalc'] === 'intval') {
2189 $content = (int)$content;
2190 }
2191 return $content;
2192 }
2193
2194 /**
2195 * char
2196 * Returns a one-character string containing the character specified by ascii code.
2197 *
2198 * Reliable results only for character codes in the integer range 0 - 127.
2199 *
2200 * @see http://php.net/manual/en/function.chr.php
2201 * @param string $content Input value undergoing processing in this function.
2202 * @param array $conf stdWrap properties for char.
2203 * @return string The processed input value
2204 */
2205 public function stdWrap_char($content = '', $conf = [])
2206 {
2207 return chr((int)$conf['char']);
2208 }
2209
2210 /**
2211 * intval
2212 * Will return an integer value of the current content
2213 *
2214 * @param string $content Input value undergoing processing in this function.
2215 * @return string The processed input value
2216 */
2217 public function stdWrap_intval($content = '')
2218 {
2219 return (int)$content;
2220 }
2221
2222 /**
2223 * Will return a hashed value of the current content
2224 *
2225 * @param string $content Input value undergoing processing in this function.
2226 * @param array $conf stdWrap properties for hash.
2227 * @return string The processed input value
2228 * @link http://php.net/manual/de/function.hash-algos.php for a list of supported hash algorithms
2229 */
2230 public function stdWrap_hash($content = '', array $conf = [])
2231 {
2232 $algorithm = isset($conf['hash.']) ? $this->stdWrap($conf['hash'], $conf['hash.']) : $conf['hash'];
2233 if (function_exists('hash') && in_array($algorithm, hash_algos())) {
2234 return hash($algorithm, $content);
2235 }
2236 // Non-existing hashing algorithm
2237 return '';
2238 }
2239
2240 /**
2241 * stdWrap_round will return a rounded number with ceil(), floor() or round(), defaults to round()
2242 * Only the english number format is supported . (dot) as decimal point
2243 *
2244 * @param string $content Input value undergoing processing in this function.
2245 * @param array $conf stdWrap properties for round.
2246 * @return string The processed input value
2247 */
2248 public function stdWrap_round($content = '', $conf = [])
2249 {
2250 return $this->round($content, $conf['round.']);
2251 }
2252
2253 /**
2254 * numberFormat
2255 * Will return a formatted number based on configuration given as stdWrap properties
2256 *
2257 * @param string $content Input value undergoing processing in this function.
2258 * @param array $conf stdWrap properties for numberFormat.
2259 * @return string The processed input value
2260 */
2261 public function stdWrap_numberFormat($content = '', $conf = [])
2262 {
2263 return $this->numberFormat($content, $conf['numberFormat.'] ?? []);
2264 }
2265
2266 /**
2267 * expandList
2268 * Will return a formatted number based on configuration given as stdWrap properties
2269 *
2270 * @param string $content Input value undergoing processing in this function.
2271 * @return string The processed input value
2272 */
2273 public function stdWrap_expandList($content = '')
2274 {
2275 return GeneralUtility::expandList($content);
2276 }
2277
2278 /**
2279 * date
2280 * Will return a formatted date based on configuration given according to PHP date/gmdate properties
2281 * Will return gmdate when the property GMT returns TRUE
2282 *
2283 * @param string $content Input value undergoing processing in this function.
2284 * @param array $conf stdWrap properties for date.
2285 * @return string The processed input value
2286 */
2287 public function stdWrap_date($content = '', $conf = [])
2288 {
2289 // Check for zero length string to mimic default case of date/gmdate.
2290 $content = (string)$content === '' ? $GLOBALS['EXEC_TIME'] : (int)$content;
2291 $content = !empty($conf['date.']['GMT']) ? gmdate($conf['date'] ?? null, $content) : date($conf['date'] ?? null, $content);
2292 return $content;
2293 }
2294
2295 /**
2296 * strftime
2297 * Will return a formatted date based on configuration given according to PHP strftime/gmstrftime properties
2298 * Will return gmstrftime when the property GMT returns TRUE
2299 *
2300 * @param string $content Input value undergoing processing in this function.
2301 * @param array $conf stdWrap properties for strftime.
2302 * @return string The processed input value
2303 */
2304 public function stdWrap_strftime($content = '', $conf = [])
2305 {
2306 // Check for zero length string to mimic default case of strtime/gmstrftime
2307 $content = (string)$content === '' ? $GLOBALS['EXEC_TIME'] : (int)$content;
2308 $content = (isset($conf['strftime.']['GMT']) && $conf['strftime.']['GMT'])
2309 ? gmstrftime($conf['strftime'] ?? null, $content)
2310 : strftime($conf['strftime'] ?? null, $content);
2311 if (!empty($conf['strftime.']['charset'])) {
2312 $output = mb_convert_encoding($content, 'utf-8', trim(strtolower($conf['strftime.']['charset'])));
2313 return $output ?: $content;
2314 }
2315 return $content;
2316 }
2317
2318 /**
2319 * strtotime
2320 * Will return a timestamp based on configuration given according to PHP strtotime
2321 *
2322 * @param string $content Input value undergoing processing in this function.
2323 * @param array $conf stdWrap properties for strtotime.
2324 * @return string The processed input value
2325 */
2326 public function stdWrap_strtotime($content = '', $conf = [])
2327 {
2328 if ($conf['strtotime'] !== '1') {
2329 $content .= ' ' . $conf['strtotime'];
2330 }
2331 return strtotime($content, $GLOBALS['EXEC_TIME']);
2332 }
2333
2334 /**
2335 * age
2336 * Will return the age of a given timestamp based on configuration given by stdWrap properties
2337 *
2338 * @param string $content Input value undergoing processing in this function.
2339 * @param array $conf stdWrap properties for age.
2340 * @return string The processed input value
2341 */
2342 public function stdWrap_age($content = '', $conf = [])
2343 {
2344 return $this->calcAge((int)($GLOBALS['EXEC_TIME'] ?? 0) - (int)$content, $conf['age'] ?? null);
2345 }
2346
2347 /**
2348 * case
2349 * Will transform the content to be upper or lower case only
2350 * Leaves HTML tags untouched
2351 *
2352 * @param string $content Input value undergoing processing in this function.
2353 * @param array $conf stdWrap properties for case.
2354 * @return string The processed input value
2355 */
2356 public function stdWrap_case($content = '', $conf = [])
2357 {
2358 return $this->HTMLcaseshift($content, $conf['case']);
2359 }
2360
2361 /**
2362 * bytes
2363 * Will return the size of a given number in Bytes *
2364 *
2365 * @param string $content Input value undergoing processing in this function.
2366 * @param array $conf stdWrap properties for bytes.
2367 * @return string The processed input value
2368 */
2369 public function stdWrap_bytes($content = '', $conf = [])
2370 {
2371 return GeneralUtility::formatSize($content, $conf['bytes.']['labels'], $conf['bytes.']['base']);
2372 }
2373
2374 /**
2375 * substring
2376 * Will return a substring based on position information given by stdWrap properties
2377 *
2378 * @param string $content Input value undergoing processing in this function.
2379 * @param array $conf stdWrap properties for substring.
2380 * @return string The processed input value
2381 */
2382 public function stdWrap_substring($content = '', $conf = [])
2383 {
2384 return $this->substring($content, $conf['substring']);
2385 }
2386
2387 /**
2388 * cropHTML
2389 * Crops content to a given size while leaving HTML tags untouched
2390 *
2391 * @param string $content Input value undergoing processing in this function.
2392 * @param array $conf stdWrap properties for cropHTML.
2393 * @return string The processed input value
2394 */
2395 public function stdWrap_cropHTML($content = '', $conf = [])
2396 {
2397 return $this->cropHTML($content, $conf['cropHTML'] ?? '');
2398 }
2399
2400 /**
2401 * stripHtml
2402 * Completely removes HTML tags from content
2403 *
2404 * @param string $content Input value undergoing processing in this function.
2405 * @return string The processed input value
2406 */
2407 public function stdWrap_stripHtml($content = '')
2408 {
2409 return strip_tags($content);
2410 }
2411
2412 /**
2413 * crop
2414 * Crops content to a given size without caring about HTML tags
2415 *
2416 * @param string $content Input value undergoing processing in this function.
2417 * @param array $conf stdWrap properties for crop.
2418 * @return string The processed input value
2419 */
2420 public function stdWrap_crop($content = '', $conf = [])
2421 {
2422 return $this->crop($content, $conf['crop']);
2423 }
2424
2425 /**
2426 * rawUrlEncode
2427 * Encodes content to be used within URLs
2428 *
2429 * @param string $content Input value undergoing processing in this function.
2430 * @return string The processed input value
2431 */
2432 public function stdWrap_rawUrlEncode($content = '')
2433 {
2434 return rawurlencode($content);
2435 }
2436
2437 /**
2438 * htmlSpecialChars
2439 * Transforms HTML tags to readable text by replacing special characters with their HTML entity
2440 * When preserveEntities returns TRUE, existing entities will be left untouched
2441 *
2442 * @param string $content Input value undergoing processing in this function.
2443 * @param array $conf stdWrap properties for htmlSpecialChars.
2444 * @return string The processed input value
2445 */
2446 public function stdWrap_htmlSpecialChars($content = '', $conf = [])
2447 {
2448 if (!empty($conf['htmlSpecialChars.']['preserveEntities'])) {
2449 $content = htmlspecialchars($content, ENT_COMPAT, 'UTF-8', false);
2450 } else {
2451 $content = htmlspecialchars($content);
2452 }
2453 return $content;
2454 }
2455
2456 /**
2457 * encodeForJavaScriptValue
2458 * Escapes content to be used inside JavaScript strings. Single quotes are added around the value.
2459 *
2460 * @param string $content Input value undergoing processing in this function
2461 * @return string The processed input value
2462 */
2463 public function stdWrap_encodeForJavaScriptValue($content = '')
2464 {
2465 return GeneralUtility::quoteJSvalue($content);
2466 }
2467
2468 /**
2469 * doubleBrTag
2470 * Searches for double line breaks and replaces them with the given value
2471 *
2472 * @param string $content Input value undergoing processing in this function.
2473 * @param array $conf stdWrap properties for doubleBrTag.
2474 * @return string The processed input value
2475 */
2476 public function stdWrap_doubleBrTag($content = '', $conf = [])
2477 {
2478 return preg_replace('/\R{1,2}[\t\x20]*\R{1,2}/', $conf['doubleBrTag'] ?? null, $content);
2479 }
2480
2481 /**
2482 * br
2483 * Searches for single line breaks and replaces them with a <br />/<br> tag
2484 * according to the doctype
2485 *
2486 * @param string $content Input value undergoing processing in this function.
2487 * @return string The processed input value
2488 */
2489 public function stdWrap_br($content = '')
2490 {
2491 return nl2br($content, !empty($this->getTypoScriptFrontendController()->xhtmlDoctype));
2492 }
2493
2494 /**
2495 * brTag
2496 * Searches for single line feeds and replaces them with the given value
2497 *
2498 * @param string $content Input value undergoing processing in this function.
2499 * @param array $conf stdWrap properties for brTag.
2500 * @return string The processed input value
2501 */
2502 public function stdWrap_brTag($content = '', $conf = [])
2503 {
2504 return str_replace(LF, $conf['brTag'] ?? null, $content);
2505 }
2506
2507 /**
2508 * encapsLines
2509 * Modifies text blocks by searching for lines which are not surrounded by HTML tags yet
2510 * and wrapping them with values given by stdWrap properties
2511 *
2512 * @param string $content Input value undergoing processing in this function.
2513 * @param array $conf stdWrap properties for erncapsLines.
2514 * @return string The processed input value
2515 */
2516 public function stdWrap_encapsLines($content = '', $conf = [])
2517 {
2518 return $this->encaps_lineSplit($content, $conf['encapsLines.']);
2519 }
2520
2521 /**
2522 * keywords
2523 * Transforms content into a CSV list to be used i.e. as keywords within a meta tag
2524 *
2525 * @param string $content Input value undergoing processing in this function.
2526 * @return string The processed input value
2527 */
2528 public function stdWrap_keywords($content = '')
2529 {
2530 return $this->keywords($content);
2531 }
2532
2533 /**
2534 * innerWrap
2535 * First of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2536 * See wrap
2537 *
2538 * @param string $content Input value undergoing processing in this function.
2539 * @param array $conf stdWrap properties for innerWrap.
2540 * @return string The processed input value
2541 */
2542 public function stdWrap_innerWrap($content = '', $conf = [])
2543 {
2544 return $this->wrap($content, $conf['innerWrap'] ?? null);
2545 }
2546
2547 /**
2548 * innerWrap2
2549 * Second of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2550 * See wrap
2551 *
2552 * @param string $content Input value undergoing processing in this function.
2553 * @param array $conf stdWrap properties for innerWrap2.
2554 * @return string The processed input value
2555 */
2556 public function stdWrap_innerWrap2($content = '', $conf = [])
2557 {
2558 return $this->wrap($content, $conf['innerWrap2'] ?? null);
2559 }
2560
2561 /**
2562 * preCObject
2563 * A content object that is prepended to the current content but between the innerWraps and the rest of the wraps
2564 *
2565 * @param string $content Input value undergoing processing in this function.
2566 * @param array $conf stdWrap properties for preCObject.
2567 * @return string The processed input value
2568 */
2569 public function stdWrap_preCObject($content = '', $conf = [])
2570 {
2571 return $this->cObjGetSingle($conf['preCObject'], $conf['preCObject.'], '/stdWrap/.preCObject') . $content;
2572 }
2573
2574 /**
2575 * postCObject
2576 * A content object that is appended to the current content but between the innerWraps and the rest of the wraps
2577 *
2578 * @param string $content Input value undergoing processing in this function.
2579 * @param array $conf stdWrap properties for postCObject.
2580 * @return string The processed input value
2581 */
2582 public function stdWrap_postCObject($content = '', $conf = [])
2583 {
2584 return $content . $this->cObjGetSingle($conf['postCObject'], $conf['postCObject.'], '/stdWrap/.postCObject');
2585 }
2586
2587 /**
2588 * wrapAlign
2589 * Wraps content with a div container having the style attribute text-align set to the given value
2590 * See wrap
2591 *
2592 * @param string $content Input value undergoing processing in this function.
2593 * @param array $conf stdWrap properties for wrapAlign.
2594 * @return string The processed input value
2595 */
2596 public function stdWrap_wrapAlign($content = '', $conf = [])
2597 {
2598 $wrapAlign = trim($conf['wrapAlign'] ?? '');
2599 if ($wrapAlign) {
2600 $content = $this->wrap($content, '<div style="text-align:' . htmlspecialchars($wrapAlign) . ';">|</div>');
2601 }
2602 return $content;
2603 }
2604
2605 /**
2606 * typolink
2607 * Wraps the content with a link tag
2608 * URLs and other attributes are created automatically by the values given in the stdWrap properties
2609 * See wrap
2610 *
2611 * @param string $content Input value undergoing processing in this function.
2612 * @param array $conf stdWrap properties for typolink.
2613 * @return string The processed input value
2614 */
2615 public function stdWrap_typolink($content = '', $conf = [])
2616 {
2617 return $this->typoLink($content, $conf['typolink.']);
2618 }
2619
2620 /**
2621 * wrap
2622 * This is the "mother" of all wraps
2623 * Third of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2624 * Basically it will put additional content before and after the current content using a split character as a placeholder for the current content
2625 * The default split character is | but it can be replaced with other characters by the property splitChar
2626 * Any other wrap that does not have own splitChar settings will be using the default split char though
2627 *
2628 * @param string $content Input value undergoing processing in this function.
2629 * @param array $conf stdWrap properties for wrap.
2630 * @return string The processed input value
2631 */
2632 public function stdWrap_wrap($content = '', $conf = [])
2633 {
2634 return $this->wrap(
2635 $content,
2636 $conf['wrap'] ?? null,
2637 $conf['wrap.']['splitChar'] ?? '|'
2638 );
2639 }
2640
2641 /**
2642 * noTrimWrap
2643 * Fourth of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2644 * The major difference to any other wrap is, that this one can make use of whitespace without trimming *
2645 *
2646 * @param string $content Input value undergoing processing in this function.
2647 * @param array $conf stdWrap properties for noTrimWrap.
2648 * @return string The processed input value
2649 */
2650 public function stdWrap_noTrimWrap($content = '', $conf = [])
2651 {
2652 $splitChar = isset($conf['noTrimWrap.']['splitChar.'])
2653 ? $this->stdWrap($conf['noTrimWrap.']['splitChar'] ?? '', $conf['noTrimWrap.']['splitChar.'])
2654 : $conf['noTrimWrap.']['splitChar'] ?? '';
2655 if ($splitChar === null || $splitChar === '') {
2656 $splitChar = '|';
2657 }
2658 $content = $this->noTrimWrap(
2659 $content,
2660 $conf['noTrimWrap'],
2661 $splitChar
2662 );
2663 return $content;
2664 }
2665
2666 /**
2667 * wrap2
2668 * Fifth of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2669 * The default split character is | but it can be replaced with other characters by the property splitChar
2670 *
2671 * @param string $content Input value undergoing processing in this function.
2672 * @param array $conf stdWrap properties for wrap2.
2673 * @return string The processed input value
2674 */
2675 public function stdWrap_wrap2($content = '', $conf = [])
2676 {
2677 return $this->wrap(
2678 $content,
2679 $conf['wrap2'] ?? null,
2680 $conf['wrap2.']['splitChar'] ?? '|'
2681 );
2682 }
2683
2684 /**
2685 * dataWrap
2686 * Sixth of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2687 * 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
2688 *
2689 * @param string $content Input value undergoing processing in this function.
2690 * @param array $conf stdWrap properties for dataWrap.
2691 * @return string The processed input value
2692 */
2693 public function stdWrap_dataWrap($content = '', $conf = [])
2694 {
2695 return $this->dataWrap($content, $conf['dataWrap']);
2696 }
2697
2698 /**
2699 * prepend
2700 * A content object that will be prepended to the current content after most of the wraps have already been applied
2701 *
2702 * @param string $content Input value undergoing processing in this function.
2703 * @param array $conf stdWrap properties for prepend.
2704 * @return string The processed input value
2705 */
2706 public function stdWrap_prepend($content = '', $conf = [])
2707 {
2708 return $this->cObjGetSingle($conf['prepend'], $conf['prepend.'], '/stdWrap/.prepend') . $content;
2709 }
2710
2711 /**
2712 * append
2713 * A content object that will be appended to the current content after most of the wraps have already been applied
2714 *
2715 * @param string $content Input value undergoing processing in this function.
2716 * @param array $conf stdWrap properties for append.
2717 * @return string The processed input value
2718 */
2719 public function stdWrap_append($content = '', $conf = [])
2720 {
2721 return $content . $this->cObjGetSingle($conf['append'], $conf['append.'], '/stdWrap/.append');
2722 }
2723
2724 /**
2725 * wrap3
2726 * Seventh of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2727 * The default split character is | but it can be replaced with other characters by the property splitChar
2728 *
2729 * @param string $content Input value undergoing processing in this function.
2730 * @param array $conf stdWrap properties for wrap3.
2731 * @return string The processed input value
2732 */
2733 public function stdWrap_wrap3($content = '', $conf = [])
2734 {
2735 return $this->wrap(
2736 $content,
2737 $conf['wrap3'] ?? null,
2738 $conf['wrap3.']['splitChar'] ?? '|'
2739 );
2740 }
2741
2742 /**
2743 * orderedStdWrap
2744 * Calls stdWrap for each entry in the provided array
2745 *
2746 * @param string $content Input value undergoing processing in this function.
2747 * @param array $conf stdWrap properties for orderedStdWrap.
2748 * @return string The processed input value
2749 */
2750 public function stdWrap_orderedStdWrap($content = '', $conf = [])
2751 {
2752 $sortedKeysArray = ArrayUtility::filterAndSortByNumericKeys($conf['orderedStdWrap.'], true);
2753 foreach ($sortedKeysArray as $key) {
2754 $content = $this->stdWrap($content, $conf['orderedStdWrap.'][$key . '.'] ?? null);
2755 }
2756 return $content;
2757 }
2758
2759 /**
2760 * outerWrap
2761 * Eighth of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2762 *
2763 * @param string $content Input value undergoing processing in this function.
2764 * @param array $conf stdWrap properties for outerWrap.
2765 * @return string The processed input value
2766 */
2767 public function stdWrap_outerWrap($content = '', $conf = [])
2768 {
2769 return $this->wrap($content, $conf['outerWrap'] ?? null);
2770 }
2771
2772 /**
2773 * insertData
2774 * Can fetch additional content the same way data does and replaces any occurrence of {field:whatever} with this content
2775 *
2776 * @param string $content Input value undergoing processing in this function.
2777 * @return string The processed input value
2778 */
2779 public function stdWrap_insertData($content = '')
2780 {
2781 return $this->insertData($content);
2782 }
2783
2784 /**
2785 * postUserFunc
2786 * Will execute a user function after the content has been modified by any other stdWrap function
2787 *
2788 * @param string $content Input value undergoing processing in this function.
2789 * @param array $conf stdWrap properties for postUserFunc.
2790 * @return string The processed input value
2791 */
2792 public function stdWrap_postUserFunc($content = '', $conf = [])
2793 {
2794 return $this->callUserFunction($conf['postUserFunc'], $conf['postUserFunc.'], $content);
2795 }
2796
2797 /**
2798 * postUserFuncInt
2799 * Will execute a user function after the content has been created and each time it is fetched from Cache
2800 * The result of this function itself will not be cached
2801 *
2802 * @param string $content Input value undergoing processing in this function.
2803 * @param array $conf stdWrap properties for postUserFuncInt.
2804 * @return string The processed input value
2805 */
2806 public function stdWrap_postUserFuncInt($content = '', $conf = [])
2807 {
2808 $substKey = 'INT_SCRIPT.' . $this->getTypoScriptFrontendController()->uniqueHash();
2809 $this->getTypoScriptFrontendController()->config['INTincScript'][$substKey] = [
2810 'content' => $content,
2811 'postUserFunc' => $conf['postUserFuncInt'],
2812 'conf' => $conf['postUserFuncInt.'],
2813 'type' => 'POSTUSERFUNC',
2814 'cObj' => serialize($this)
2815 ];
2816 $content = '<!--' . $substKey . '-->';
2817 return $content;
2818 }
2819
2820 /**
2821 * prefixComment
2822 * Will add HTML comments to the content to make it easier to identify certain content elements within the HTML output later on
2823 *
2824 * @param string $content Input value undergoing processing in this function.
2825 * @param array $conf stdWrap properties for prefixComment.
2826 * @return string The processed input value
2827 */
2828 public function stdWrap_prefixComment($content = '', $conf = [])
2829 {
2830 if (
2831 (!isset($this->getTypoScriptFrontendController()->config['config']['disablePrefixComment']) || !$this->getTypoScriptFrontendController()->config['config']['disablePrefixComment'])
2832 && !empty($conf['prefixComment'])
2833 ) {
2834 $content = $this->prefixComment($conf['prefixComment'], [], $content);
2835 }
2836 return $content;
2837 }
2838
2839 /**
2840 * editIcons
2841 * Will render icons for frontend editing as long as there is a BE user logged in
2842 *
2843 * @param string $content Input value undergoing processing in this function.
2844 * @param array $conf stdWrap properties for editIcons.
2845 * @return string The processed input value
2846 */
2847 public function stdWrap_editIcons($content = '', $conf = [])
2848 {
2849 if ($this->getTypoScriptFrontendController()->isBackendUserLoggedIn() && $conf['editIcons']) {
2850 if (!isset($conf['editIcons.']) || !is_array($conf['editIcons.'])) {
2851 $conf['editIcons.'] = [];
2852 }
2853 $content = $this->editIcons($content, $conf['editIcons'], $conf['editIcons.']);
2854 }
2855 return $content;
2856 }
2857
2858 /**
2859 * editPanel
2860 * Will render the edit panel for frontend editing as long as there is a BE user logged in
2861 *
2862 * @param string $content Input value undergoing processing in this function.
2863 * @param array $conf stdWrap properties for editPanel.
2864 * @return string The processed input value
2865 */
2866 public function stdWrap_editPanel($content = '', $conf = [])
2867 {
2868 if ($this->getTypoScriptFrontendController()->isBackendUserLoggedIn()) {
2869 $content = $this->editPanel($content, $conf['editPanel.']);
2870 }
2871 return $content;
2872 }
2873
2874 /**
2875 * Store content into cache
2876 *
2877 * @param string $content Input value undergoing processing in these functions.
2878 * @param array $conf All stdWrap properties, not just the ones for a particular function.
2879 * @return string The processed input value
2880 */
2881 public function stdWrap_cacheStore($content = '', $conf = [])
2882 {
2883 if (!isset($conf['cache.'])) {
2884 return $content;
2885 }
2886 $key = $this->calculateCacheKey($conf['cache.']);
2887 if (empty($key)) {
2888 return $content;
2889 }
2890 /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cacheFrontend */
2891 $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)->getCache('hash');
2892 $tags = $this->calculateCacheTags($conf['cache.']);
2893 $lifetime = $this->calculateCacheLifetime($conf['cache.']);
2894 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap_cacheStore'] ?? [] as $_funcRef) {
2895 $params = [
2896 'key' => $key,
2897 'content' => $content,
2898 'lifetime' => $lifetime,
2899 'tags' => $tags
2900 ];
2901 $ref = $this; // introduced for phpstan to not lose type information when passing $this into callUserFunction
2902 GeneralUtility::callUserFunction($_funcRef, $params, $ref);
2903 }
2904 $cacheFrontend->set($key, $content, $tags, $lifetime);
2905 return $content;
2906 }
2907
2908 /**
2909 * stdWrap post process hook
2910 * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
2911 * this hook executes functions at after the content has been modified by the rest of the stdWrap functions but still before debugging
2912 *
2913 * @param string $content Input value undergoing processing in these functions.
2914 * @param array $conf All stdWrap properties, not just the ones for a particular function.
2915 * @return string The processed input value
2916 */
2917 public function stdWrap_stdWrapPostProcess($content = '', $conf = [])
2918 {
2919 foreach ($this->stdWrapHookObjects as $hookObject) {
2920 /** @var ContentObjectStdWrapHookInterface $hookObject */
2921 $content = $hookObject->stdWrapPostProcess($content, $conf, $this);
2922 }
2923 return $content;
2924 }
2925
2926 /**
2927 * debug
2928 * Will output the content as readable HTML code
2929 *
2930 * @param string $content Input value undergoing processing in this function.
2931 * @return string The processed input value
2932 */
2933 public function stdWrap_debug($content = '')
2934 {
2935 return '<pre>' . htmlspecialchars($content) . '</pre>';
2936 }
2937
2938 /**
2939 * debugFunc
2940 * Will output the content in a debug table
2941 *
2942 * @param string $content Input value undergoing processing in this function.
2943 * @param array $conf stdWrap properties for debugFunc.
2944 * @return string The processed input value
2945 */
2946 public function stdWrap_debugFunc($content = '', $conf = [])
2947 {
2948 debug((int)$conf['debugFunc'] === 2 ? [$content] : $content);
2949 return $content;
2950 }
2951
2952 /**
2953 * debugData
2954 * Will output the data used by the current record in a debug table
2955 *
2956 * @param string $content Input value undergoing processing in this function.
2957 * @return string The processed input value
2958 */
2959 public function stdWrap_debugData($content = '')
2960 {
2961 debug($this->data, '$cObj->data:');
2962 if (is_array($this->alternativeData)) {
2963 debug($this->alternativeData, '$this->alternativeData');
2964 }
2965 return $content;
2966 }
2967
2968 /**
2969 * Returns number of rows selected by the query made by the properties set.
2970 * Implements the stdWrap "numRows" property
2971 *
2972 * @param array $conf TypoScript properties for the property (see link to "numRows")
2973 * @return int The number of rows found by the select
2974 * @internal
2975 * @see stdWrap()
2976 */
2977 public function numRows($conf)
2978 {
2979 $conf['select.']['selectFields'] = 'count(*)';
2980 $statement = $this->exec_getQuery($conf['table'], $conf['select.']);
2981
2982 return (int)$statement->fetchColumn(0);
2983 }
2984
2985 /**
2986 * Exploding a string by the $char value (if integer its an ASCII value) and returning index $listNum
2987 *
2988 * @param string $content String to explode
2989 * @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())
2990 * @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.
2991 * @return string
2992 */