5318b9394c0c0b3ae7181b80431fc2ec0dc8bc0d
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Classes / ContentObject / ContentObjectRenderer.php
1 <?php
2 namespace TYPO3\CMS\Frontend\ContentObject;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use Doctrine\DBAL\DBALException;
18 use Doctrine\DBAL\Driver\Statement;
19 use Psr\Log\LoggerAwareInterface;
20 use Psr\Log\LoggerAwareTrait;
21 use TYPO3\CMS\Core\Cache\CacheManager;
22 use TYPO3\CMS\Core\Context\Context;
23 use TYPO3\CMS\Core\Context\LanguageAspect;
24 use TYPO3\CMS\Core\Core\Environment;
25 use TYPO3\CMS\Core\Database\Connection;
26 use TYPO3\CMS\Core\Database\ConnectionPool;
27 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
28 use TYPO3\CMS\Core\Database\Query\QueryHelper;
29 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
30 use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
31 use TYPO3\CMS\Core\Html\HtmlParser;
32 use TYPO3\CMS\Core\Imaging\ImageManipulation\Area;
33 use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
34 use TYPO3\CMS\Core\LinkHandling\LinkService;
35 use TYPO3\CMS\Core\Log\LogManager;
36 use TYPO3\CMS\Core\Mail\MailMessage;
37 use TYPO3\CMS\Core\Resource\Exception;
38 use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
39 use TYPO3\CMS\Core\Resource\File;
40 use TYPO3\CMS\Core\Resource\FileInterface;
41 use TYPO3\CMS\Core\Resource\FileReference;
42 use TYPO3\CMS\Core\Resource\Folder;
43 use TYPO3\CMS\Core\Resource\ProcessedFile;
44 use TYPO3\CMS\Core\Resource\ResourceFactory;
45 use TYPO3\CMS\Core\Resource\StorageRepository;
46 use TYPO3\CMS\Core\Service\DependencyOrderingService;
47 use TYPO3\CMS\Core\Service\FlexFormService;
48 use TYPO3\CMS\Core\Service\MarkerBasedTemplateService;
49 use TYPO3\CMS\Core\Site\Entity\Site;
50 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
51 use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
52 use TYPO3\CMS\Core\TypoScript\TypoScriptService;
53 use TYPO3\CMS\Core\Utility\ArrayUtility;
54 use TYPO3\CMS\Core\Utility\DebugUtility;
55 use TYPO3\CMS\Core\Utility\GeneralUtility;
56 use TYPO3\CMS\Core\Utility\MailUtility;
57 use TYPO3\CMS\Core\Utility\MathUtility;
58 use TYPO3\CMS\Core\Utility\PathUtility;
59 use TYPO3\CMS\Core\Utility\StringUtility;
60 use TYPO3\CMS\Core\Versioning\VersionState;
61 use TYPO3\CMS\Frontend\ContentObject\Exception\ContentRenderingException;
62 use TYPO3\CMS\Frontend\ContentObject\Exception\ExceptionHandlerInterface;
63 use TYPO3\CMS\Frontend\ContentObject\Exception\ProductionExceptionHandler;
64 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
65 use TYPO3\CMS\Frontend\Http\UrlProcessorInterface;
66 use TYPO3\CMS\Frontend\Imaging\GifBuilder;
67 use TYPO3\CMS\Frontend\Page\PageRepository;
68 use TYPO3\CMS\Frontend\Resource\FilePathSanitizer;
69 use TYPO3\CMS\Frontend\Service\TypoLinkCodecService;
70 use TYPO3\CMS\Frontend\Typolink\AbstractTypolinkBuilder;
71 use TYPO3\CMS\Frontend\Typolink\UnableToLinkException;
72
73 /**
74 * This class contains all main TypoScript features.
75 * This includes the rendering of TypoScript content objects (cObjects).
76 * Is the backbone of TypoScript Template rendering.
77 *
78 * There are lots of functions you can use from your include-scripts.
79 * The class is normally instantiated and referred to as "cObj".
80 * When you call your own PHP-code typically through a USER or USER_INT cObject then it is this class that instantiates the object and calls the main method. Before it does so it will set (if you are using classes) a reference to itself in the internal variable "cObj" of the object. Thus you can access all functions and data from this class by $this->cObj->... from within you classes written to be USER or USER_INT content objects.
81 */
82 class ContentObjectRenderer implements LoggerAwareInterface
83 {
84 use LoggerAwareTrait;
85
86 /**
87 * @var array
88 */
89 public $align = [
90 'center',
91 'right',
92 'left'
93 ];
94
95 /**
96 * stdWrap functions in their correct order
97 *
98 * @see stdWrap()
99 */
100 public $stdWrapOrder = [
101 'stdWrapPreProcess' => 'hook',
102 // this is a placeholder for the first Hook
103 'cacheRead' => 'hook',
104 // this is a placeholder for checking if the content is available in cache
105 'setContentToCurrent' => 'boolean',
106 'setContentToCurrent.' => 'array',
107 'addPageCacheTags' => 'string',
108 'addPageCacheTags.' => 'array',
109 'setCurrent' => 'string',
110 'setCurrent.' => 'array',
111 'lang.' => 'array',
112 'data' => 'getText',
113 'data.' => 'array',
114 'field' => 'fieldName',
115 'field.' => 'array',
116 'current' => 'boolean',
117 'current.' => 'array',
118 'cObject' => 'cObject',
119 'cObject.' => 'array',
120 'numRows.' => 'array',
121 'filelist' => 'dir',
122 'filelist.' => 'array',
123 'preUserFunc' => 'functionName',
124 'stdWrapOverride' => 'hook',
125 // this is a placeholder for the second Hook
126 'override' => 'string',
127 'override.' => 'array',
128 'preIfEmptyListNum' => 'listNum',
129 'preIfEmptyListNum.' => 'array',
130 'ifNull' => 'string',
131 'ifNull.' => 'array',
132 'ifEmpty' => 'string',
133 'ifEmpty.' => 'array',
134 'ifBlank' => 'string',
135 'ifBlank.' => 'array',
136 'listNum' => 'listNum',
137 'listNum.' => 'array',
138 'trim' => 'boolean',
139 'trim.' => 'array',
140 'strPad.' => 'array',
141 'stdWrap' => 'stdWrap',
142 'stdWrap.' => 'array',
143 'stdWrapProcess' => 'hook',
144 // this is a placeholder for the third Hook
145 'required' => 'boolean',
146 'required.' => 'array',
147 'if.' => 'array',
148 'fieldRequired' => 'fieldName',
149 'fieldRequired.' => 'array',
150 'csConv' => 'string',
151 'csConv.' => 'array',
152 'parseFunc' => 'objectpath',
153 'parseFunc.' => 'array',
154 'HTMLparser' => 'boolean',
155 'HTMLparser.' => 'array',
156 'split.' => 'array',
157 'replacement.' => 'array',
158 'prioriCalc' => 'boolean',
159 'prioriCalc.' => 'array',
160 'char' => 'integer',
161 'char.' => 'array',
162 'intval' => 'boolean',
163 'intval.' => 'array',
164 'hash' => 'string',
165 'hash.' => 'array',
166 'round' => 'boolean',
167 'round.' => 'array',
168 'numberFormat.' => 'array',
169 'expandList' => 'boolean',
170 'expandList.' => 'array',
171 'date' => 'dateconf',
172 'date.' => 'array',
173 'strtotime' => 'strtotimeconf',
174 'strtotime.' => 'array',
175 'strftime' => 'strftimeconf',
176 'strftime.' => 'array',
177 'age' => 'boolean',
178 'age.' => 'array',
179 'case' => 'case',
180 'case.' => 'array',
181 'bytes' => 'boolean',
182 'bytes.' => 'array',
183 'substring' => 'parameters',
184 'substring.' => 'array',
185 'cropHTML' => 'crop',
186 'cropHTML.' => 'array',
187 'stripHtml' => 'boolean',
188 'stripHtml.' => 'array',
189 'crop' => 'crop',
190 'crop.' => 'array',
191 'rawUrlEncode' => 'boolean',
192 'rawUrlEncode.' => 'array',
193 'htmlSpecialChars' => 'boolean',
194 'htmlSpecialChars.' => 'array',
195 'encodeForJavaScriptValue' => 'boolean',
196 'encodeForJavaScriptValue.' => 'array',
197 'doubleBrTag' => 'string',
198 'doubleBrTag.' => 'array',
199 'br' => 'boolean',
200 'br.' => 'array',
201 'brTag' => 'string',
202 'brTag.' => 'array',
203 'encapsLines.' => 'array',
204 'keywords' => 'boolean',
205 'keywords.' => 'array',
206 'innerWrap' => 'wrap',
207 'innerWrap.' => 'array',
208 'innerWrap2' => 'wrap',
209 'innerWrap2.' => 'array',
210 'addParams.' => 'array',
211 'filelink.' => 'array',
212 'preCObject' => 'cObject',
213 'preCObject.' => 'array',
214 'postCObject' => 'cObject',
215 'postCObject.' => 'array',
216 'wrapAlign' => 'align',
217 'wrapAlign.' => 'array',
218 'typolink.' => 'array',
219 'wrap' => 'wrap',
220 'wrap.' => 'array',
221 'noTrimWrap' => 'wrap',
222 'noTrimWrap.' => 'array',
223 'wrap2' => 'wrap',
224 'wrap2.' => 'array',
225 'dataWrap' => 'dataWrap',
226 'dataWrap.' => 'array',
227 'prepend' => 'cObject',
228 'prepend.' => 'array',
229 'append' => 'cObject',
230 'append.' => 'array',
231 'wrap3' => 'wrap',
232 'wrap3.' => 'array',
233 'orderedStdWrap' => 'stdWrap',
234 'orderedStdWrap.' => 'array',
235 'outerWrap' => 'wrap',
236 'outerWrap.' => 'array',
237 'insertData' => 'boolean',
238 'insertData.' => 'array',
239 'postUserFunc' => 'functionName',
240 'postUserFuncInt' => 'functionName',
241 'prefixComment' => 'string',
242 'prefixComment.' => 'array',
243 'editIcons' => 'string',
244 'editIcons.' => 'array',
245 'editPanel' => 'boolean',
246 'editPanel.' => 'array',
247 'cacheStore' => 'hook',
248 // this is a placeholder for storing the content in cache
249 'stdWrapPostProcess' => 'hook',
250 // this is a placeholder for the last Hook
251 'debug' => 'boolean',
252 'debug.' => 'array',
253 'debugFunc' => 'boolean',
254 'debugFunc.' => 'array',
255 'debugData' => 'boolean',
256 'debugData.' => 'array'
257 ];
258
259 /**
260 * Class names for accordant content object names
261 *
262 * @var array
263 */
264 protected $contentObjectClassMap = [];
265
266 /**
267 * Loaded with the current data-record.
268 *
269 * If the instance of this class is used to render records from the database those records are found in this array.
270 * The function stdWrap has TypoScript properties that fetch field-data from this array.
271 *
272 * @var array
273 * @see start()
274 */
275 public $data = [];
276
277 /**
278 * @var string
279 */
280 protected $table = '';
281
282 /**
283 * Used for backup
284 *
285 * @var array
286 */
287 public $oldData = [];
288
289 /**
290 * If this is set with an array before stdWrap, it's used instead of $this->data in the data-property in stdWrap
291 *
292 * @var string
293 */
294 public $alternativeData = '';
295
296 /**
297 * Used by the parseFunc function and is loaded with tag-parameters when parsing tags.
298 *
299 * @var array
300 */
301 public $parameters = [];
302
303 /**
304 * @var string
305 */
306 public $currentValKey = 'currentValue_kidjls9dksoje';
307
308 /**
309 * This is set to the [table]:[uid] of the record delivered in the $data-array, if the cObjects CONTENT or RECORD is in operation.
310 * Note that $GLOBALS['TSFE']->currentRecord is set to an equal value but always indicating the latest record rendered.
311 *
312 * @var string
313 */
314 public $currentRecord = '';
315
316 /**
317 * Set in RecordsContentObject and ContentContentObject to the current number of records selected in a query.
318 *
319 * @var int
320 */
321 public $currentRecordTotal = 0;
322
323 /**
324 * Incremented in RecordsContentObject and ContentContentObject before each record rendering.
325 *
326 * @var int
327 */
328 public $currentRecordNumber = 0;
329
330 /**
331 * Incremented in RecordsContentObject and ContentContentObject before each record rendering.
332 *
333 * @var int
334 */
335 public $parentRecordNumber = 0;
336
337 /**
338 * 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.
339 *
340 * @var array
341 */
342 public $parentRecord = [];
343
344 /**
345 * 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.
346 *
347 * @var array
348 */
349 public $checkPid_cache = [];
350
351 /**
352 * @var string
353 */
354 public $checkPid_badDoktypeList = '255';
355
356 /**
357 * This will be set by typoLink() to the url of the most recent link created.
358 *
359 * @var string
360 */
361 public $lastTypoLinkUrl = '';
362
363 /**
364 * DO. link target.
365 *
366 * @var string
367 */
368 public $lastTypoLinkTarget = '';
369
370 /**
371 * @var array
372 */
373 public $lastTypoLinkLD = [];
374
375 /**
376 * array that registers rendered content elements (or any table) to make sure they are not rendered recursively!
377 *
378 * @var array
379 */
380 public $recordRegister = [];
381
382 /**
383 * Additionally registered content object types and class names
384 *
385 * @var array
386 */
387 protected $cObjHookObjectsRegistry = [];
388
389 /**
390 * @var array
391 */
392 public $cObjHookObjectsArr = [];
393
394 /**
395 * Containing hook objects for stdWrap
396 *
397 * @var array
398 */
399 protected $stdWrapHookObjects = [];
400
401 /**
402 * Containing hook objects for getImgResource
403 *
404 * @var array
405 */
406 protected $getImgResourceHookObjects;
407
408 /**
409 * @var File Current file objects (during iterations over files)
410 */
411 protected $currentFile;
412
413 /**
414 * Set to TRUE by doConvertToUserIntObject() if USER object wants to become USER_INT
415 */
416 public $doConvertToUserIntObject = false;
417
418 /**
419 * Indicates current object type. Can hold one of OBJECTTYPE_ constants or FALSE.
420 * The value is set and reset inside USER() function. Any time outside of
421 * USER() it is FALSE.
422 */
423 protected $userObjectType = false;
424
425 /**
426 * @var array
427 */
428 protected $stopRendering = [];
429
430 /**
431 * @var int
432 */
433 protected $stdWrapRecursionLevel = 0;
434
435 /**
436 * @var TypoScriptFrontendController
437 */
438 protected $typoScriptFrontendController;
439
440 /**
441 * Indicates that object type is USER.
442 *
443 * @see ContentObjectRender::$userObjectType
444 */
445 const OBJECTTYPE_USER_INT = 1;
446 /**
447 * Indicates that object type is USER.
448 *
449 * @see ContentObjectRender::$userObjectType
450 */
451 const OBJECTTYPE_USER = 2;
452
453 /**
454 * @param TypoScriptFrontendController $typoScriptFrontendController
455 */
456 public function __construct(TypoScriptFrontendController $typoScriptFrontendController = null)
457 {
458 $this->typoScriptFrontendController = $typoScriptFrontendController;
459 $this->contentObjectClassMap = $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'];
460 }
461
462 /**
463 * Prevent several objects from being serialized.
464 * If currentFile is set, it is either a File or a FileReference object. As the object itself can't be serialized,
465 * we have store a hash and restore the object in __wakeup()
466 *
467 * @return array
468 */
469 public function __sleep()
470 {
471 $vars = get_object_vars($this);
472 unset($vars['typoScriptFrontendController'], $vars['logger']);
473 if ($this->currentFile instanceof FileReference) {
474 $this->currentFile = 'FileReference:' . $this->currentFile->getUid();
475 } elseif ($this->currentFile instanceof File) {
476 $this->currentFile = 'File:' . $this->currentFile->getIdentifier();
477 } else {
478 unset($vars['currentFile']);
479 }
480 return array_keys($vars);
481 }
482
483 /**
484 * Restore currentFile from hash.
485 * If currentFile references a File, the identifier equals file identifier.
486 * If it references a FileReference the identifier equals the uid of the reference.
487 */
488 public function __wakeup()
489 {
490 if (isset($GLOBALS['TSFE'])) {
491 $this->typoScriptFrontendController = $GLOBALS['TSFE'];
492 }
493 if ($this->currentFile !== null && is_string($this->currentFile)) {
494 list($objectType, $identifier) = explode(':', $this->currentFile, 2);
495 try {
496 if ($objectType === 'File') {
497 $this->currentFile = ResourceFactory::getInstance()->retrieveFileOrFolderObject($identifier);
498 } elseif ($objectType === 'FileReference') {
499 $this->currentFile = ResourceFactory::getInstance()->getFileReferenceObject($identifier);
500 }
501 } catch (ResourceDoesNotExistException $e) {
502 $this->currentFile = null;
503 }
504 }
505 $this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
506 }
507
508 /**
509 * Allow injecting content object class map.
510 *
511 * This method is private API, please use configuration
512 * $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'] to add new content objects
513 *
514 * @internal
515 * @param array $contentObjectClassMap
516 */
517 public function setContentObjectClassMap(array $contentObjectClassMap)
518 {
519 $this->contentObjectClassMap = $contentObjectClassMap;
520 }
521
522 /**
523 * Register a single content object name to class name
524 *
525 * This method is private API, please use configuration
526 * $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'] to add new content objects
527 *
528 * @param string $className
529 * @param string $contentObjectName
530 * @internal
531 */
532 public function registerContentObjectClass($className, $contentObjectName)
533 {
534 $this->contentObjectClassMap[$contentObjectName] = $className;
535 }
536
537 /**
538 * Class constructor.
539 * Well, it has to be called manually since it is not a real constructor function.
540 * 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.
541 *
542 * @param array $data The record data that is rendered.
543 * @param string $table The table that the data record is from.
544 */
545 public function start($data, $table = '')
546 {
547 $this->data = $data;
548 $this->table = $table;
549 $this->currentRecord = $table !== ''
550 ? $table . ':' . ($this->data['uid'] ?? '')
551 : '';
552 $this->parameters = [];
553 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['cObjTypeAndClass'] ?? [] as $classArr) {
554 $this->cObjHookObjectsRegistry[$classArr[0]] = $classArr[1];
555 }
556 $this->stdWrapHookObjects = [];
557 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap'] ?? [] as $className) {
558 $hookObject = GeneralUtility::makeInstance($className);
559 if (!$hookObject instanceof ContentObjectStdWrapHookInterface) {
560 throw new \UnexpectedValueException($className . ' must implement interface ' . ContentObjectStdWrapHookInterface::class, 1195043965);
561 }
562 $this->stdWrapHookObjects[] = $hookObject;
563 }
564 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['postInit'] ?? [] as $className) {
565 $postInitializationProcessor = GeneralUtility::makeInstance($className);
566 if (!$postInitializationProcessor instanceof ContentObjectPostInitHookInterface) {
567 throw new \UnexpectedValueException($className . ' must implement interface ' . ContentObjectPostInitHookInterface::class, 1274563549);
568 }
569 $postInitializationProcessor->postProcessContentObjectInitialization($this);
570 }
571 }
572
573 /**
574 * Returns the current table
575 *
576 * @return string
577 */
578 public function getCurrentTable()
579 {
580 return $this->table;
581 }
582
583 /**
584 * Gets the 'getImgResource' hook objects.
585 * The first call initializes the accordant objects.
586 *
587 * @return array The 'getImgResource' hook objects (if any)
588 */
589 protected function getGetImgResourceHookObjects()
590 {
591 if (!isset($this->getImgResourceHookObjects)) {
592 $this->getImgResourceHookObjects = [];
593 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImgResource'] ?? [] as $className) {
594 $hookObject = GeneralUtility::makeInstance($className);
595 if (!$hookObject instanceof ContentObjectGetImageResourceHookInterface) {
596 throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetImageResourceHookInterface::class, 1218636383);
597 }
598 $this->getImgResourceHookObjects[] = $hookObject;
599 }
600 }
601 return $this->getImgResourceHookObjects;
602 }
603
604 /**
605 * Sets the internal variable parentRecord with information about current record.
606 * 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.
607 *
608 * @param array $data The record array
609 * @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.
610 * @access private
611 */
612 public function setParent($data, $currentRecord)
613 {
614 $this->parentRecord = [
615 'data' => $data,
616 'currentRecord' => $currentRecord
617 ];
618 }
619
620 /***********************************************
621 *
622 * CONTENT_OBJ:
623 *
624 ***********************************************/
625 /**
626 * Returns the "current" value.
627 * 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.
628 * It's like "load accumulator" in the good old C64 days... basically a "register" you can use as you like.
629 * 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.
630 *
631 * @return mixed The "current" value
632 */
633 public function getCurrentVal()
634 {
635 return $this->data[$this->currentValKey];
636 }
637
638 /**
639 * Sets the "current" value.
640 *
641 * @param mixed $value The variable that you want to set as "current
642 * @see getCurrentVal()
643 */
644 public function setCurrentVal($value)
645 {
646 $this->data[$this->currentValKey] = $value;
647 }
648
649 /**
650 * Rendering of a "numerical array" of cObjects from TypoScript
651 * Will call ->cObjGetSingle() for each cObject found and accumulate the output.
652 *
653 * @param array $setup array with cObjects as values.
654 * @param string $addKey A prefix for the debugging information
655 * @return string Rendered output from the cObjects in the array.
656 * @see cObjGetSingle()
657 */
658 public function cObjGet($setup, $addKey = '')
659 {
660 if (!is_array($setup)) {
661 return '';
662 }
663 $sKeyArray = ArrayUtility::filterAndSortByNumericKeys($setup);
664 $content = '';
665 foreach ($sKeyArray as $theKey) {
666 $theValue = $setup[$theKey];
667 if ((int)$theKey && strpos($theKey, '.') === false) {
668 $conf = $setup[$theKey . '.'];
669 $content .= $this->cObjGetSingle($theValue, $conf, $addKey . $theKey);
670 }
671 }
672 return $content;
673 }
674
675 /**
676 * Renders a content object
677 *
678 * @param string $name The content object name, eg. "TEXT" or "USER" or "IMAGE
679 * @param array $conf The array with TypoScript properties for the content object
680 * @param string $TSkey A string label used for the internal debugging tracking.
681 * @return string cObject output
682 * @throws \UnexpectedValueException
683 */
684 public function cObjGetSingle($name, $conf, $TSkey = '__')
685 {
686 $content = '';
687 // Checking that the function is not called eternally. This is done by interrupting at a depth of 100
688 $this->getTypoScriptFrontendController()->cObjectDepthCounter--;
689 if ($this->getTypoScriptFrontendController()->cObjectDepthCounter > 0) {
690 $timeTracker = $this->getTimeTracker();
691 $name = trim($name);
692 if ($timeTracker->LR) {
693 $timeTracker->push($TSkey, $name);
694 }
695 // Checking if the COBJ is a reference to another object. (eg. name of 'blabla.blabla = < styles.something')
696 if (isset($name[0]) && $name[0] === '<') {
697 $key = trim(substr($name, 1));
698 $cF = GeneralUtility::makeInstance(TypoScriptParser::class);
699 // $name and $conf is loaded with the referenced values.
700 $confOverride = is_array($conf) ? $conf : [];
701 list($name, $conf) = $cF->getVal($key, $this->getTypoScriptFrontendController()->tmpl->setup);
702 $conf = array_replace_recursive(is_array($conf) ? $conf : [], $confOverride);
703 // Getting the cObject
704 $timeTracker->incStackPointer();
705 $content .= $this->cObjGetSingle($name, $conf, $key);
706 $timeTracker->decStackPointer();
707 } else {
708 $hooked = false;
709 // Application defined cObjects
710 if (!empty($this->cObjHookObjectsRegistry[$name])) {
711 if (empty($this->cObjHookObjectsArr[$name])) {
712 $this->cObjHookObjectsArr[$name] = GeneralUtility::makeInstance($this->cObjHookObjectsRegistry[$name]);
713 }
714 $hookObj = $this->cObjHookObjectsArr[$name];
715 if (method_exists($hookObj, 'cObjGetSingleExt')) {
716 $content .= $hookObj->cObjGetSingleExt($name, $conf, $TSkey, $this);
717 $hooked = true;
718 }
719 }
720 if (!$hooked) {
721 $contentObject = $this->getContentObject($name);
722 if ($contentObject) {
723 $content .= $this->render($contentObject, $conf);
724 } else {
725 // Call hook functions for extra processing
726 if ($name) {
727 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['cObjTypeAndClassDefault'] ?? [] as $className) {
728 $hookObject = GeneralUtility::makeInstance($className);
729 if (!$hookObject instanceof ContentObjectGetSingleHookInterface) {
730 throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetSingleHookInterface::class, 1195043731);
731 }
732 /** @var ContentObjectGetSingleHookInterface $hookObject */
733 $content .= $hookObject->getSingleContentObject($name, (array)$conf, $TSkey, $this);
734 }
735 } else {
736 // Log error in AdminPanel
737 $warning = sprintf('Content Object "%s" does not exist', $name);
738 $timeTracker->setTSlogMessage($warning, 2);
739 }
740 }
741 }
742 }
743 if ($timeTracker->LR) {
744 $timeTracker->pull($content);
745 }
746 }
747 // Increasing on exit...
748 $this->getTypoScriptFrontendController()->cObjectDepthCounter++;
749 return $content;
750 }
751
752 /**
753 * Returns a new content object of type $name.
754 * This content object needs to be registered as content object
755 * in $this->contentObjectClassMap
756 *
757 * @param string $name
758 * @return AbstractContentObject|null
759 * @throws ContentRenderingException
760 */
761 public function getContentObject($name)
762 {
763 if (!isset($this->contentObjectClassMap[$name])) {
764 return null;
765 }
766 $fullyQualifiedClassName = $this->contentObjectClassMap[$name];
767 $contentObject = GeneralUtility::makeInstance($fullyQualifiedClassName, $this);
768 if (!($contentObject instanceof AbstractContentObject)) {
769 throw new ContentRenderingException(sprintf('Registered content object class name "%s" must be an instance of AbstractContentObject, but is not!', $fullyQualifiedClassName), 1422564295);
770 }
771 return $contentObject;
772 }
773
774 /********************************************
775 *
776 * Functions rendering content objects (cObjects)
777 *
778 ********************************************/
779
780 /**
781 * Renders a content object by taking exception and cache handling
782 * into consideration
783 *
784 * @param AbstractContentObject $contentObject Content object instance
785 * @param array $configuration Array of TypoScript properties
786 *
787 * @throws ContentRenderingException
788 * @throws \Exception
789 * @return string
790 */
791 public function render(AbstractContentObject $contentObject, $configuration = [])
792 {
793 $content = '';
794
795 // Evaluate possible cache and return
796 $cacheConfiguration = $configuration['cache.'] ?? null;
797 if ($cacheConfiguration !== null) {
798 unset($configuration['cache.']);
799 $cache = $this->getFromCache($cacheConfiguration);
800 if ($cache !== false) {
801 return $cache;
802 }
803 }
804
805 // Render content
806 try {
807 $content .= $contentObject->render($configuration);
808 } catch (ContentRenderingException $exception) {
809 // Content rendering Exceptions indicate a critical problem which should not be
810 // caught e.g. when something went wrong with Exception handling itself
811 throw $exception;
812 } catch (\Exception $exception) {
813 $exceptionHandler = $this->createExceptionHandler($configuration);
814 if ($exceptionHandler === null) {
815 throw $exception;
816 }
817 $content = $exceptionHandler->handle($exception, $contentObject, $configuration);
818 }
819
820 // Store cache
821 if ($cacheConfiguration !== null) {
822 $key = $this->calculateCacheKey($cacheConfiguration);
823 if (!empty($key)) {
824 /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cacheFrontend */
825 $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash');
826 $tags = $this->calculateCacheTags($cacheConfiguration);
827 $lifetime = $this->calculateCacheLifetime($cacheConfiguration);
828 $cacheFrontend->set($key, $content, $tags, $lifetime);
829 }
830 }
831
832 return $content;
833 }
834
835 /**
836 * Creates the content object exception handler from local content object configuration
837 * or, from global configuration if not explicitly disabled in local configuration
838 *
839 * @param array $configuration
840 * @return ExceptionHandlerInterface|null
841 * @throws ContentRenderingException
842 */
843 protected function createExceptionHandler($configuration = [])
844 {
845 $exceptionHandler = null;
846 $exceptionHandlerClassName = $this->determineExceptionHandlerClassName($configuration);
847 if (!empty($exceptionHandlerClassName)) {
848 $exceptionHandler = GeneralUtility::makeInstance($exceptionHandlerClassName, $this->mergeExceptionHandlerConfiguration($configuration));
849 if (!$exceptionHandler instanceof ExceptionHandlerInterface) {
850 throw new ContentRenderingException('An exception handler was configured but the class does not exist or does not implement the ExceptionHandlerInterface', 1403653369);
851 }
852 }
853
854 return $exceptionHandler;
855 }
856
857 /**
858 * Determine exception handler class name from global and content object configuration
859 *
860 * @param array $configuration
861 * @return string|null
862 */
863 protected function determineExceptionHandlerClassName($configuration)
864 {
865 $exceptionHandlerClassName = null;
866 $tsfe = $this->getTypoScriptFrontendController();
867 if (!isset($tsfe->config['config']['contentObjectExceptionHandler'])) {
868 if (GeneralUtility::getApplicationContext()->isProduction()) {
869 $exceptionHandlerClassName = '1';
870 }
871 } else {
872 $exceptionHandlerClassName = $tsfe->config['config']['contentObjectExceptionHandler'];
873 }
874
875 if (isset($configuration['exceptionHandler'])) {
876 $exceptionHandlerClassName = $configuration['exceptionHandler'];
877 }
878
879 if ($exceptionHandlerClassName === '1') {
880 $exceptionHandlerClassName = ProductionExceptionHandler::class;
881 }
882
883 return $exceptionHandlerClassName;
884 }
885
886 /**
887 * Merges global exception handler configuration with the one from the content object
888 * and returns the merged exception handler configuration
889 *
890 * @param array $configuration
891 * @return array
892 */
893 protected function mergeExceptionHandlerConfiguration($configuration)
894 {
895 $exceptionHandlerConfiguration = [];
896 $tsfe = $this->getTypoScriptFrontendController();
897 if (!empty($tsfe->config['config']['contentObjectExceptionHandler.'])) {
898 $exceptionHandlerConfiguration = $tsfe->config['config']['contentObjectExceptionHandler.'];
899 }
900 if (!empty($configuration['exceptionHandler.'])) {
901 $exceptionHandlerConfiguration = array_replace_recursive($exceptionHandlerConfiguration, $configuration['exceptionHandler.']);
902 }
903
904 return $exceptionHandlerConfiguration;
905 }
906
907 /**
908 * Retrieves a type of object called as USER or USER_INT. Object can detect their
909 * type by using this call. It returns OBJECTTYPE_USER_INT or OBJECTTYPE_USER depending on the
910 * current object execution. In all other cases it will return FALSE to indicate
911 * a call out of context.
912 *
913 * @return mixed One of OBJECTTYPE_ class constants or FALSE
914 */
915 public function getUserObjectType()
916 {
917 return $this->userObjectType;
918 }
919
920 /**
921 * Sets the user object type
922 *
923 * @param mixed $userObjectType
924 */
925 public function setUserObjectType($userObjectType)
926 {
927 $this->userObjectType = $userObjectType;
928 }
929
930 /**
931 * Requests the current USER object to be converted to USER_INT.
932 */
933 public function convertToUserIntObject()
934 {
935 if ($this->userObjectType !== self::OBJECTTYPE_USER) {
936 $this->getTimeTracker()->setTSlogMessage(self::class . '::convertToUserIntObject() is called in the wrong context or for the wrong object type', 2);
937 } else {
938 $this->doConvertToUserIntObject = true;
939 }
940 }
941
942 /************************************
943 *
944 * Various helper functions for content objects:
945 *
946 ************************************/
947 /**
948 * Converts a given config in Flexform to a conf-array
949 *
950 * @param string|array $flexData Flexform data
951 * @param array $conf Array to write the data into, by reference
952 * @param bool $recursive Is set if called recursive. Don't call function with this parameter, it's used inside the function only
953 */
954 public function readFlexformIntoConf($flexData, &$conf, $recursive = false)
955 {
956 if ($recursive === false && is_string($flexData)) {
957 $flexData = GeneralUtility::xml2array($flexData, 'T3');
958 }
959 if (is_array($flexData) && isset($flexData['data']['sDEF']['lDEF'])) {
960 $flexData = $flexData['data']['sDEF']['lDEF'];
961 }
962 if (!is_array($flexData)) {
963 return;
964 }
965 foreach ($flexData as $key => $value) {
966 if (!is_array($value)) {
967 continue;
968 }
969 if (isset($value['el'])) {
970 if (is_array($value['el']) && !empty($value['el'])) {
971 foreach ($value['el'] as $ekey => $element) {
972 if (isset($element['vDEF'])) {
973 $conf[$ekey] = $element['vDEF'];
974 } else {
975 if (is_array($element)) {
976 $this->readFlexformIntoConf($element, $conf[$key][key($element)][$ekey], true);
977 } else {
978 $this->readFlexformIntoConf($element, $conf[$key][$ekey], true);
979 }
980 }
981 }
982 } else {
983 $this->readFlexformIntoConf($value['el'], $conf[$key], true);
984 }
985 }
986 if (isset($value['vDEF'])) {
987 $conf[$key] = $value['vDEF'];
988 }
989 }
990 }
991
992 /**
993 * Returns all parents of the given PID (Page UID) list
994 *
995 * @param string $pidList A list of page Content-Element PIDs (Page UIDs) / stdWrap
996 * @param array $pidConf stdWrap array for the list
997 * @return string A list of PIDs
998 * @access private
999 */
1000 public function getSlidePids($pidList, $pidConf)
1001 {
1002 $pidList = isset($pidConf) ? trim($this->stdWrap($pidList, $pidConf)) : trim($pidList);
1003 if ($pidList === '') {
1004 $pidList = 'this';
1005 }
1006 $tsfe = $this->getTypoScriptFrontendController();
1007 $listArr = null;
1008 if (trim($pidList)) {
1009 $listArr = GeneralUtility::intExplode(',', str_replace('this', $tsfe->contentPid, $pidList));
1010 $listArr = $this->checkPidArray($listArr);
1011 }
1012 $pidList = [];
1013 if (is_array($listArr) && !empty($listArr)) {
1014 foreach ($listArr as $uid) {
1015 $page = $tsfe->sys_page->getPage($uid);
1016 if (!$page['is_siteroot']) {
1017 $pidList[] = $page['pid'];
1018 }
1019 }
1020 }
1021 return implode(',', $pidList);
1022 }
1023
1024 /**
1025 * Returns a <img> tag with the image file defined by $file and processed according to the properties in the TypoScript array.
1026 * Mostly this function is a sub-function to the IMAGE function which renders the IMAGE cObject in TypoScript.
1027 * This function is called by "$this->cImage($conf['file'], $conf);" from IMAGE().
1028 *
1029 * @param string $file File TypoScript resource
1030 * @param array $conf TypoScript configuration properties
1031 * @return string <img> tag, (possibly wrapped in links and other HTML) if any image found.
1032 * @access private
1033 * @see IMAGE()
1034 */
1035 public function cImage($file, $conf)
1036 {
1037 $tsfe = $this->getTypoScriptFrontendController();
1038 $info = $this->getImgResource($file, $conf['file.']);
1039 $tsfe->lastImageInfo = $info;
1040 if (!is_array($info)) {
1041 return '';
1042 }
1043 if (is_file(Environment::getPublicPath() . '/' . $info['3'])) {
1044 $source = $tsfe->absRefPrefix . str_replace('%2F', '/', rawurlencode($info['3']));
1045 } else {
1046 $source = $info[3];
1047 }
1048
1049 $layoutKey = $this->stdWrap($conf['layoutKey'], $conf['layoutKey.']);
1050 $imageTagTemplate = $this->getImageTagTemplate($layoutKey, $conf);
1051 $sourceCollection = $this->getImageSourceCollection($layoutKey, $conf, $file);
1052
1053 // This array is used to collect the image-refs on the page...
1054 $tsfe->imagesOnPage[] = $source;
1055 $altParam = $this->getAltParam($conf);
1056 $params = $this->stdWrapValue('params', $conf);
1057 if ($params !== '' && $params[0] !== ' ') {
1058 $params = ' ' . $params;
1059 }
1060
1061 $imageTagValues = [
1062 'width' => (int)$info[0],
1063 'height' => (int)$info[1],
1064 'src' => htmlspecialchars($source),
1065 'params' => $params,
1066 'altParams' => $altParam,
1067 'border' => $this->getBorderAttr(' border="' . (int)$conf['border'] . '"'),
1068 'sourceCollection' => $sourceCollection,
1069 'selfClosingTagSlash' => !empty($tsfe->xhtmlDoctype) ? ' /' : '',
1070 ];
1071
1072 $markerTemplateEngine = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
1073 $theValue = $markerTemplateEngine->substituteMarkerArray($imageTagTemplate, $imageTagValues, '###|###', true, true);
1074
1075 $linkWrap = isset($conf['linkWrap.']) ? $this->stdWrap($conf['linkWrap'], $conf['linkWrap.']) : $conf['linkWrap'];
1076 if ($linkWrap) {
1077 $theValue = $this->linkWrap($theValue, $linkWrap);
1078 } elseif ($conf['imageLinkWrap']) {
1079 $originalFile = !empty($info['originalFile']) ? $info['originalFile'] : $info['origFile'];
1080 $theValue = $this->imageLinkWrap($theValue, $originalFile, $conf['imageLinkWrap.']);
1081 }
1082 $wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
1083 if ((string)$wrap !== '') {
1084 $theValue = $this->wrap($theValue, $conf['wrap']);
1085 }
1086 return $theValue;
1087 }
1088
1089 /**
1090 * Returns the 'border' attribute for an <img> tag only if the doctype is not xhtml_strict, xhtml_11 or html5
1091 * or if the config parameter 'disableImgBorderAttr' is not set.
1092 *
1093 * @param string $borderAttr The border attribute
1094 * @return string The border attribute
1095 */
1096 public function getBorderAttr($borderAttr)
1097 {
1098 $tsfe = $this->getTypoScriptFrontendController();
1099 $docType = $tsfe->xhtmlDoctype;
1100 if (
1101 $docType !== 'xhtml_strict' && $docType !== 'xhtml_11'
1102 && $tsfe->config['config']['doctype'] !== 'html5'
1103 && !$tsfe->config['config']['disableImgBorderAttr']
1104 ) {
1105 return $borderAttr;
1106 }
1107 return '';
1108 }
1109
1110 /**
1111 * Returns the html-template for rendering the image-Tag if no template is defined via typoscript the
1112 * default <img> tag template is returned
1113 *
1114 * @param string $layoutKey rendering key
1115 * @param array $conf TypoScript configuration properties
1116 * @return string
1117 */
1118 public function getImageTagTemplate($layoutKey, $conf)
1119 {
1120 if ($layoutKey && isset($conf['layout.']) && isset($conf['layout.'][$layoutKey . '.'])) {
1121 $imageTagLayout = $this->stdWrap(
1122 $conf['layout.'][$layoutKey . '.']['element'] ?? '',
1123 $conf['layout.'][$layoutKey . '.']['element.'] ?? []
1124 );
1125 } else {
1126 $imageTagLayout = '<img src="###SRC###" width="###WIDTH###" height="###HEIGHT###" ###PARAMS### ###ALTPARAMS### ###BORDER######SELFCLOSINGTAGSLASH###>';
1127 }
1128 return $imageTagLayout;
1129 }
1130
1131 /**
1132 * Render alternate sources for the image tag. If no source collection is given an empty string is returned.
1133 *
1134 * @param string $layoutKey rendering key
1135 * @param array $conf TypoScript configuration properties
1136 * @param string $file
1137 * @throws \UnexpectedValueException
1138 * @return string
1139 */
1140 public function getImageSourceCollection($layoutKey, $conf, $file)
1141 {
1142 $sourceCollection = '';
1143 if ($layoutKey
1144 && isset($conf['sourceCollection.']) && $conf['sourceCollection.']
1145 && (
1146 isset($conf['layout.'][$layoutKey . '.']['source']) && $conf['layout.'][$layoutKey . '.']['source']
1147 || isset($conf['layout.'][$layoutKey . '.']['source.']) && $conf['layout.'][$layoutKey . '.']['source.']
1148 )
1149 ) {
1150
1151 // find active sourceCollection
1152 $activeSourceCollections = [];
1153 foreach ($conf['sourceCollection.'] as $sourceCollectionKey => $sourceCollectionConfiguration) {
1154 if (substr($sourceCollectionKey, -1) === '.') {
1155 if (empty($sourceCollectionConfiguration['if.']) || $this->checkIf($sourceCollectionConfiguration['if.'])) {
1156 $activeSourceCollections[] = $sourceCollectionConfiguration;
1157 }
1158 }
1159 }
1160
1161 // apply option split to configurations
1162 $tsfe = $this->getTypoScriptFrontendController();
1163 $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
1164 $srcLayoutOptionSplitted = $typoScriptService->explodeConfigurationForOptionSplit((array)$conf['layout.'][$layoutKey . '.'], count($activeSourceCollections));
1165
1166 // render sources
1167 foreach ($activeSourceCollections as $key => $sourceConfiguration) {
1168 $sourceLayout = $this->stdWrap(
1169 $srcLayoutOptionSplitted[$key]['source'] ?? '',
1170 $srcLayoutOptionSplitted[$key]['source.'] ?? []
1171 );
1172
1173 $sourceRenderConfiguration = [
1174 'file' => $file,
1175 'file.' => $conf['file.'] ?? null
1176 ];
1177
1178 if (isset($sourceConfiguration['quality']) || isset($sourceConfiguration['quality.'])) {
1179 $imageQuality = $sourceConfiguration['quality'] ?? '';
1180 if (isset($sourceConfiguration['quality.'])) {
1181 $imageQuality = $this->stdWrap($sourceConfiguration['quality'], $sourceConfiguration['quality.']);
1182 }
1183 if ($imageQuality) {
1184 $sourceRenderConfiguration['file.']['params'] = '-quality ' . (int)$imageQuality;
1185 }
1186 }
1187
1188 if (isset($sourceConfiguration['pixelDensity'])) {
1189 $pixelDensity = (int)$this->stdWrap(
1190 $sourceConfiguration['pixelDensity'] ?? '',
1191 $sourceConfiguration['pixelDensity.'] ?? []
1192 );
1193 } else {
1194 $pixelDensity = 1;
1195 }
1196 $dimensionKeys = ['width', 'height', 'maxW', 'minW', 'maxH', 'minH', 'maxWidth', 'maxHeight', 'XY'];
1197 foreach ($dimensionKeys as $dimensionKey) {
1198 $dimension = $this->stdWrap(
1199 $sourceConfiguration[$dimensionKey] ?? '',
1200 $sourceConfiguration[$dimensionKey . '.'] ?? []
1201 );
1202 if (!$dimension) {
1203 $dimension = $this->stdWrap(
1204 $conf['file.'][$dimensionKey] ?? '',
1205 $conf['file.'][$dimensionKey . '.'] ?? []
1206 );
1207 }
1208 if ($dimension) {
1209 if (strstr($dimension, 'c') !== false && ($dimensionKey === 'width' || $dimensionKey === 'height')) {
1210 $dimensionParts = explode('c', $dimension, 2);
1211 $dimension = ((int)$dimensionParts[0] * $pixelDensity) . 'c';
1212 if ($dimensionParts[1]) {
1213 $dimension .= $dimensionParts[1];
1214 }
1215 } elseif ($dimensionKey === 'XY') {
1216 $dimensionParts = GeneralUtility::intExplode(',', $dimension, false, 2);
1217 $dimension = $dimensionParts[0] * $pixelDensity;
1218 if ($dimensionParts[1]) {
1219 $dimension .= ',' . $dimensionParts[1] * $pixelDensity;
1220 }
1221 } else {
1222 $dimension = (int)$dimension * $pixelDensity;
1223 }
1224 $sourceRenderConfiguration['file.'][$dimensionKey] = $dimension;
1225 // Remove the stdWrap properties for dimension as they have been processed already above.
1226 unset($sourceRenderConfiguration['file.'][$dimensionKey . '.']);
1227 }
1228 }
1229 $sourceInfo = $this->getImgResource($sourceRenderConfiguration['file'], $sourceRenderConfiguration['file.']);
1230 if ($sourceInfo) {
1231 $sourceConfiguration['width'] = $sourceInfo[0];
1232 $sourceConfiguration['height'] = $sourceInfo[1];
1233 $urlPrefix = '';
1234 if (parse_url($sourceInfo[3], PHP_URL_HOST) === null) {
1235 $urlPrefix = $tsfe->absRefPrefix;
1236 }
1237 $sourceConfiguration['src'] = htmlspecialchars($urlPrefix . $sourceInfo[3]);
1238 $sourceConfiguration['selfClosingTagSlash'] = !empty($tsfe->xhtmlDoctype) ? ' /' : '';
1239
1240 $markerTemplateEngine = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
1241 $oneSourceCollection = $markerTemplateEngine->substituteMarkerArray($sourceLayout, $sourceConfiguration, '###|###', true, true);
1242
1243 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImageSourceCollection'] ?? [] as $className) {
1244 $hookObject = GeneralUtility::makeInstance($className);
1245 if (!$hookObject instanceof ContentObjectOneSourceCollectionHookInterface) {
1246 throw new \UnexpectedValueException(
1247 '$hookObject must implement interface ' . ContentObjectOneSourceCollectionHookInterface::class,
1248 1380007853
1249 );
1250 }
1251 $oneSourceCollection = $hookObject->getOneSourceCollection((array)$sourceRenderConfiguration, (array)$sourceConfiguration, $oneSourceCollection, $this);
1252 }
1253
1254 $sourceCollection .= $oneSourceCollection;
1255 }
1256 }
1257 }
1258 return $sourceCollection;
1259 }
1260
1261 /**
1262 * Wraps the input string in link-tags that opens the image in a new window.
1263 *
1264 * @param string $string String to wrap, probably an <img> tag
1265 * @param string|File|FileReference $imageFile The original image file
1266 * @param array $conf TypoScript properties for the "imageLinkWrap" function
1267 * @return string The input string, $string, wrapped as configured.
1268 * @see cImage()
1269 */
1270 public function imageLinkWrap($string, $imageFile, $conf)
1271 {
1272 $string = (string)$string;
1273 $enable = isset($conf['enable.']) ? $this->stdWrap($conf['enable'], $conf['enable.']) : $conf['enable'];
1274 if (!$enable) {
1275 return $string;
1276 }
1277 $content = (string)$this->typoLink($string, $conf['typolink.']);
1278 if (isset($conf['file.'])) {
1279 $imageFile = $this->stdWrap($imageFile, $conf['file.']);
1280 }
1281
1282 if ($imageFile instanceof File) {
1283 $file = $imageFile;
1284 } elseif ($imageFile instanceof FileReference) {
1285 $file = $imageFile->getOriginalFile();
1286 } else {
1287 if (MathUtility::canBeInterpretedAsInteger($imageFile)) {
1288 $file = ResourceFactory::getInstance()->getFileObject((int)$imageFile);
1289 } else {
1290 $file = ResourceFactory::getInstance()->getFileObjectFromCombinedIdentifier($imageFile);
1291 }
1292 }
1293
1294 // Create imageFileLink if not created with typolink
1295 if ($content === $string) {
1296 $parameterNames = ['width', 'height', 'effects', 'bodyTag', 'title', 'wrap', 'crop'];
1297 $parameters = [];
1298 $sample = isset($conf['sample.']) ? $this->stdWrap($conf['sample'], $conf['sample.']) : $conf['sample'];
1299 if ($sample) {
1300 $parameters['sample'] = 1;
1301 }
1302 foreach ($parameterNames as $parameterName) {
1303 if (isset($conf[$parameterName . '.'])) {
1304 $conf[$parameterName] = $this->stdWrap($conf[$parameterName], $conf[$parameterName . '.']);
1305 }
1306 if (isset($conf[$parameterName]) && $conf[$parameterName]) {
1307 $parameters[$parameterName] = $conf[$parameterName];
1308 }
1309 }
1310 $parametersEncoded = base64_encode(serialize($parameters));
1311 $hmac = GeneralUtility::hmac(implode('|', [$file->getUid(), $parametersEncoded]));
1312 $params = '&md5=' . $hmac;
1313 foreach (str_split($parametersEncoded, 64) as $index => $chunk) {
1314 $params .= '&parameters' . rawurlencode('[') . $index . rawurlencode(']') . '=' . rawurlencode($chunk);
1315 }
1316 $url = $this->getTypoScriptFrontendController()->absRefPrefix . 'index.php?eID=tx_cms_showpic&file=' . $file->getUid() . $params;
1317 $directImageLink = isset($conf['directImageLink.']) ? $this->stdWrap($conf['directImageLink'], $conf['directImageLink.']) : $conf['directImageLink'];
1318 if ($directImageLink) {
1319 $imgResourceConf = [
1320 'file' => $imageFile,
1321 'file.' => $conf
1322 ];
1323 $url = $this->cObjGetSingle('IMG_RESOURCE', $imgResourceConf);
1324 if (!$url) {
1325 // If no imagemagick / gm is available
1326 $url = $imageFile;
1327 }
1328 }
1329 // Create TARGET-attribute only if the right doctype is used
1330 $target = '';
1331 $xhtmlDocType = $this->getTypoScriptFrontendController()->xhtmlDoctype;
1332 if ($xhtmlDocType !== 'xhtml_strict' && $xhtmlDocType !== 'xhtml_11') {
1333 $target = isset($conf['target.'])
1334 ? (string)$this->stdWrap($conf['target'], $conf['target.'])
1335 : (string)$conf['target'];
1336 if ($target === '') {
1337 $target = 'thePicture';
1338 }
1339 }
1340 $a1 = '';
1341 $a2 = '';
1342 $conf['JSwindow'] = isset($conf['JSwindow.']) ? $this->stdWrap($conf['JSwindow'], $conf['JSwindow.']) : $conf['JSwindow'];
1343 if ($conf['JSwindow']) {
1344 if ($conf['JSwindow.']['altUrl'] || $conf['JSwindow.']['altUrl.']) {
1345 $altUrl = isset($conf['JSwindow.']['altUrl.']) ? $this->stdWrap($conf['JSwindow.']['altUrl'], $conf['JSwindow.']['altUrl.']) : $conf['JSwindow.']['altUrl'];
1346 if ($altUrl) {
1347 $url = $altUrl . ($conf['JSwindow.']['altUrl_noDefaultParams'] ? '' : '?file=' . rawurlencode($imageFile) . $params);
1348 }
1349 }
1350
1351 $processedFile = $file->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $conf);
1352 $JSwindowExpand = isset($conf['JSwindow.']['expand.']) ? $this->stdWrap($conf['JSwindow.']['expand'], $conf['JSwindow.']['expand.']) : $conf['JSwindow.']['expand'];
1353 $offset = GeneralUtility::intExplode(',', $JSwindowExpand . ',');
1354 $newWindow = isset($conf['JSwindow.']['newWindow.']) ? $this->stdWrap($conf['JSwindow.']['newWindow'], $conf['JSwindow.']['newWindow.']) : $conf['JSwindow.']['newWindow'];
1355 $onClick = 'openPic('
1356 . GeneralUtility::quoteJSvalue($this->getTypoScriptFrontendController()->baseUrlWrap($url)) . ','
1357 . '\'' . ($newWindow ? md5($url) : 'thePicture') . '\','
1358 . GeneralUtility::quoteJSvalue('width=' . ($processedFile->getProperty('width') + $offset[0])
1359 . ',height=' . ($processedFile->getProperty('height') + $offset[1]) . ',status=0,menubar=0')
1360 . '); return false;';
1361 $a1 = '<a href="' . htmlspecialchars($url) . '"'
1362 . ' onclick="' . htmlspecialchars($onClick) . '"'
1363 . ($target !== '' ? ' target="' . htmlspecialchars($target) . '"' : '')
1364 . $this->getTypoScriptFrontendController()->ATagParams . '>';
1365 $a2 = '</a>';
1366 $this->getTypoScriptFrontendController()->setJS('openPic');
1367 } else {
1368 $conf['linkParams.']['parameter'] = $url;
1369 $string = $this->typoLink($string, $conf['linkParams.']);
1370 }
1371 if (isset($conf['stdWrap.'])) {
1372 $string = $this->stdWrap($string, $conf['stdWrap.']);
1373 }
1374 $content = $a1 . $string . $a2;
1375 }
1376 return $content;
1377 }
1378
1379 /**
1380 * Sets the SYS_LASTCHANGED timestamp if input timestamp is larger than current value.
1381 * The SYS_LASTCHANGED timestamp can be used by various caching/indexing applications to determine if the page has new content.
1382 * Therefore you should call this function with the last-changed timestamp of any element you display.
1383 *
1384 * @param int $tstamp Unix timestamp (number of seconds since 1970)
1385 * @see TypoScriptFrontendController::setSysLastChanged()
1386 */
1387 public function lastChanged($tstamp)
1388 {
1389 $tstamp = (int)$tstamp;
1390 $tsfe = $this->getTypoScriptFrontendController();
1391 if ($tstamp > (int)$tsfe->register['SYS_LASTCHANGED']) {
1392 $tsfe->register['SYS_LASTCHANGED'] = $tstamp;
1393 }
1394 }
1395
1396 /**
1397 * Wraps the input string by the $wrap value and implements the "linkWrap" data type as well.
1398 * 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.
1399 *
1400 * @param string $content Input string
1401 * @param string $wrap A string where the first two parts separated by "|" (vertical line) will be wrapped around the input string
1402 * @return string Wrapped output string
1403 * @see wrap(), cImage(), FILE()
1404 */
1405 public function linkWrap($content, $wrap)
1406 {
1407 $wrapArr = explode('|', $wrap);
1408 if (preg_match('/\\{([0-9]*)\\}/', $wrapArr[0], $reg)) {
1409 $uid = $this->getTypoScriptFrontendController()->tmpl->rootLine[$reg[1]]['uid'] ?? null;
1410 if ($uid) {
1411 $wrapArr[0] = str_replace($reg[0], $uid, $wrapArr[0]);
1412 }
1413 }
1414 return trim($wrapArr[0] ?? '') . $content . trim($wrapArr[1] ?? '');
1415 }
1416
1417 /**
1418 * An abstraction method which creates an alt or title parameter for an HTML img, applet, area or input element and the FILE content element.
1419 * From the $conf array it implements the properties "altText", "titleText" and "longdescURL"
1420 *
1421 * @param array $conf TypoScript configuration properties
1422 * @param bool $longDesc If set, the longdesc attribute will be generated - must only be used for img elements!
1423 * @return string Parameter string containing alt and title parameters (if any)
1424 * @see IMGTEXT(), FILE(), FORM(), cImage(), filelink()
1425 */
1426 public function getAltParam($conf, $longDesc = true)
1427 {
1428 $altText = isset($conf['altText.']) ? trim($this->stdWrap($conf['altText'], $conf['altText.'])) : trim($conf['altText']);
1429 $titleText = isset($conf['titleText.']) ? trim($this->stdWrap($conf['titleText'], $conf['titleText.'])) : trim($conf['titleText']);
1430 if (isset($conf['longdescURL.']) && $this->getTypoScriptFrontendController()->config['config']['doctype'] !== 'html5') {
1431 $longDescUrl = $this->typoLink_URL($conf['longdescURL.']);
1432 } else {
1433 $longDescUrl = trim($conf['longdescURL']);
1434 }
1435 $longDescUrl = strip_tags($longDescUrl);
1436
1437 // "alt":
1438 $altParam = ' alt="' . htmlspecialchars($altText) . '"';
1439 // "title":
1440 $emptyTitleHandling = isset($conf['emptyTitleHandling.']) ? $this->stdWrap($conf['emptyTitleHandling'], $conf['emptyTitleHandling.']) : $conf['emptyTitleHandling'];
1441 // Choices: 'keepEmpty' | 'useAlt' | 'removeAttr'
1442 if ($titleText || $emptyTitleHandling === 'keepEmpty') {
1443 $altParam .= ' title="' . htmlspecialchars($titleText) . '"';
1444 } elseif (!$titleText && $emptyTitleHandling === 'useAlt') {
1445 $altParam .= ' title="' . htmlspecialchars($altText) . '"';
1446 }
1447 // "longDesc" URL
1448 if ($longDesc && !empty($longDescUrl)) {
1449 $altParam .= ' longdesc="' . htmlspecialchars($longDescUrl) . '"';
1450 }
1451 return $altParam;
1452 }
1453
1454 /**
1455 * An abstraction method to add parameters to an A tag.
1456 * Uses the ATagParams property.
1457 *
1458 * @param array $conf TypoScript configuration properties
1459 * @param bool|int $addGlobal If set, will add the global config.ATagParams to the link
1460 * @return string String containing the parameters to the A tag (if non empty, with a leading space)
1461 * @see IMGTEXT(), filelink(), makelinks(), typolink()
1462 */
1463 public function getATagParams($conf, $addGlobal = 1)
1464 {
1465 $aTagParams = '';
1466 if ($conf['ATagParams.'] ?? false) {
1467 $aTagParams = ' ' . $this->stdWrap($conf['ATagParams'], $conf['ATagParams.']);
1468 } elseif ($conf['ATagParams'] ?? false) {
1469 $aTagParams = ' ' . $conf['ATagParams'];
1470 }
1471 if ($addGlobal) {
1472 $aTagParams = ' ' . trim($this->getTypoScriptFrontendController()->ATagParams . $aTagParams);
1473 }
1474 // Extend params
1475 $_params = [
1476 'conf' => &$conf,
1477 'aTagParams' => &$aTagParams
1478 ];
1479 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getATagParamsPostProc'] ?? [] as $className) {
1480 $processor = & GeneralUtility::makeInstance($className);
1481 $aTagParams = $processor->process($_params, $this);
1482 }
1483
1484 $aTagParams = trim($aTagParams);
1485 if (!empty($aTagParams)) {
1486 $aTagParams = ' ' . $aTagParams;
1487 }
1488
1489 return $aTagParams;
1490 }
1491
1492 /**
1493 * All extension links should ask this function for additional properties to their tags.
1494 * Designed to add for instance an "onclick" property for site tracking systems.
1495 *
1496 * @param string $URL URL of the website
1497 * @param string $TYPE
1498 * @return string The additional tag properties
1499 */
1500 public function extLinkATagParams($URL, $TYPE)
1501 {
1502 $out = '';
1503 if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['extLinkATagParamsHandler'])) {
1504 $extLinkATagParamsHandler = GeneralUtility::makeInstance($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['extLinkATagParamsHandler']);
1505 if (method_exists($extLinkATagParamsHandler, 'main')) {
1506 $out .= trim($extLinkATagParamsHandler->main($URL, $TYPE, $this));
1507 }
1508 }
1509 return trim($out) ? ' ' . trim($out) : '';
1510 }
1511
1512 /***********************************************
1513 *
1514 * HTML template processing functions
1515 *
1516 ***********************************************/
1517
1518 /**
1519 * Sets the current file object during iterations over files.
1520 *
1521 * @param File $fileObject The file object.
1522 */
1523 public function setCurrentFile($fileObject)
1524 {
1525 $this->currentFile = $fileObject;
1526 }
1527
1528 /**
1529 * Gets the current file object during iterations over files.
1530 *
1531 * @return File The current file object.
1532 */
1533 public function getCurrentFile()
1534 {
1535 return $this->currentFile;
1536 }
1537
1538 /***********************************************
1539 *
1540 * "stdWrap" + sub functions
1541 *
1542 ***********************************************/
1543 /**
1544 * The "stdWrap" function. This is the implementation of what is known as "stdWrap properties" in TypoScript.
1545 * Basically "stdWrap" performs some processing of a value based on properties in the input $conf array(holding the TypoScript "stdWrap properties")
1546 * 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.
1547 *
1548 * If $this->alternativeData is an array it's used instead of the $this->data array in ->getData
1549 *
1550 * @param string $content Input value undergoing processing in this function. Possibly substituted by other values fetched from another source.
1551 * @param array $conf TypoScript "stdWrap properties".
1552 * @return string The processed input value
1553 */
1554 public function stdWrap($content = '', $conf = [])
1555 {
1556 $content = (string)$content;
1557 // If there is any hook object, activate all of the process and override functions.
1558 // The hook interface ContentObjectStdWrapHookInterface takes care that all 4 methods exist.
1559 if ($this->stdWrapHookObjects) {
1560 $conf['stdWrapPreProcess'] = 1;
1561 $conf['stdWrapOverride'] = 1;
1562 $conf['stdWrapProcess'] = 1;
1563 $conf['stdWrapPostProcess'] = 1;
1564 }
1565
1566 if (!is_array($conf) || !$conf) {
1567 return $content;
1568 }
1569
1570 // Cache handling
1571 if (isset($conf['cache.']) && is_array($conf['cache.'])) {
1572 $conf['cache.']['key'] = $this->stdWrap($conf['cache.']['key'], $conf['cache.']['key.']);
1573 $conf['cache.']['tags'] = $this->stdWrap($conf['cache.']['tags'], $conf['cache.']['tags.']);
1574 $conf['cache.']['lifetime'] = $this->stdWrap($conf['cache.']['lifetime'], $conf['cache.']['lifetime.']);
1575 $conf['cacheRead'] = 1;
1576 $conf['cacheStore'] = 1;
1577 }
1578 // The configuration is sorted and filtered by intersection with the defined stdWrapOrder.
1579 $sortedConf = array_keys(array_intersect_key($this->stdWrapOrder, $conf));
1580 // Functions types that should not make use of nested stdWrap function calls to avoid conflicts with internal TypoScript used by these functions
1581 $stdWrapDisabledFunctionTypes = 'cObject,functionName,stdWrap';
1582 // Additional Array to check whether a function has already been executed
1583 $isExecuted = [];
1584 // Additional switch to make sure 'required', 'if' and 'fieldRequired'
1585 // will still stop rendering immediately in case they return FALSE
1586 $this->stdWrapRecursionLevel++;
1587 $this->stopRendering[$this->stdWrapRecursionLevel] = false;
1588 // execute each function in the predefined order
1589 foreach ($sortedConf as $stdWrapName) {
1590 // eliminate the second key of a pair 'key'|'key.' to make sure functions get called only once and check if rendering has been stopped
1591 if ((!isset($isExecuted[$stdWrapName]) || !$isExecuted[$stdWrapName]) && !$this->stopRendering[$this->stdWrapRecursionLevel]) {
1592 $functionName = rtrim($stdWrapName, '.');
1593 $functionProperties = $functionName . '.';
1594 $functionType = $this->stdWrapOrder[$functionName] ?? null;
1595 // If there is any code on the next level, check if it contains "official" stdWrap functions
1596 // if yes, execute them first - will make each function stdWrap aware
1597 // so additional stdWrap calls within the functions can be removed, since the result will be the same
1598 if (!empty($conf[$functionProperties]) && !GeneralUtility::inList($stdWrapDisabledFunctionTypes, $functionType)) {
1599 if (array_intersect_key($this->stdWrapOrder, $conf[$functionProperties])) {
1600 $conf[$functionName] = $this->stdWrap($conf[$functionName] ?? '', $conf[$functionProperties] ?? []);
1601 }
1602 }
1603 // Check if key is still containing something, since it might have been changed by next level stdWrap before
1604 if ((isset($conf[$functionName]) || $conf[$functionProperties])
1605 && ($functionType !== 'boolean' || $conf[$functionName])
1606 ) {
1607 // Get just that part of $conf that is needed for the particular function
1608 $singleConf = [
1609 $functionName => $conf[$functionName] ?? null,
1610 $functionProperties => $conf[$functionProperties] ?? null
1611 ];
1612 // Hand over the whole $conf array to the stdWrapHookObjects
1613 if ($functionType === 'hook') {
1614 $singleConf = $conf;
1615 }
1616 // Add both keys - with and without the dot - to the set of executed functions
1617 $isExecuted[$functionName] = true;
1618 $isExecuted[$functionProperties] = true;
1619 // Call the function with the prefix stdWrap_ to make sure nobody can execute functions just by adding their name to the TS Array
1620 $functionName = 'stdWrap_' . $functionName;
1621 $content = $this->{$functionName}($content, $singleConf);
1622 } elseif ($functionType === 'boolean' && !$conf[$functionName]) {
1623 $isExecuted[$functionName] = true;
1624 $isExecuted[$functionProperties] = true;
1625 }
1626 }
1627 }
1628 unset($this->stopRendering[$this->stdWrapRecursionLevel]);
1629 $this->stdWrapRecursionLevel--;
1630
1631 return $content;
1632 }
1633
1634 /**
1635 * Gets a configuration value by passing them through stdWrap first and taking a default value if stdWrap doesn't yield a result.
1636 *
1637 * @param string $key The config variable key (from TS array).
1638 * @param array $config The TypoScript array.
1639 * @param string $defaultValue Optional default value.
1640 * @return string Value of the config variable
1641 */
1642 public function stdWrapValue($key, array $config, $defaultValue = '')
1643 {
1644 if (isset($config[$key])) {
1645 if (!isset($config[$key . '.'])) {
1646 return $config[$key];
1647 }
1648 } elseif (isset($config[$key . '.'])) {
1649 $config[$key] = '';
1650 } else {
1651 return $defaultValue;
1652 }
1653 $stdWrapped = $this->stdWrap($config[$key], $config[$key . '.']);
1654 return $stdWrapped ?: $defaultValue;
1655 }
1656
1657 /**
1658 * stdWrap pre process hook
1659 * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
1660 * this hook will execute functions before any other stdWrap function can modify anything
1661 *
1662 * @param string $content Input value undergoing processing in these functions.
1663 * @param array $conf All stdWrap properties, not just the ones for a particular function.
1664 * @return string The processed input value
1665 */
1666 public function stdWrap_stdWrapPreProcess($content = '', $conf = [])
1667 {
1668 foreach ($this->stdWrapHookObjects as $hookObject) {
1669 /** @var ContentObjectStdWrapHookInterface $hookObject */
1670 $content = $hookObject->stdWrapPreProcess($content, $conf, $this);
1671 }
1672 return $content;
1673 }
1674
1675 /**
1676 * Check if content was cached before (depending on the given cache key)
1677 *
1678 * @param string $content Input value undergoing processing in these functions.
1679 * @param array $conf All stdWrap properties, not just the ones for a particular function.
1680 * @return string The processed input value
1681 */
1682 public function stdWrap_cacheRead($content = '', $conf = [])
1683 {
1684 if (!isset($conf['cache.'])) {
1685 return $content;
1686 }
1687 $result = $this->getFromCache($conf['cache.']);
1688 return $result === false ? $content : $result;
1689 }
1690
1691 /**
1692 * Add tags to page cache (comma-separated list)
1693 *
1694 * @param string $content Input value undergoing processing in these functions.
1695 * @param array $conf All stdWrap properties, not just the ones for a particular function.
1696 * @return string The processed input value
1697 */
1698 public function stdWrap_addPageCacheTags($content = '', $conf = [])
1699 {
1700 $tags = isset($conf['addPageCacheTags.'])
1701 ? $this->stdWrap($conf['addPageCacheTags'], $conf['addPageCacheTags.'])
1702 : $conf['addPageCacheTags'];
1703 if (!empty($tags)) {
1704 $cacheTags = GeneralUtility::trimExplode(',', $tags, true);
1705 $this->getTypoScriptFrontendController()->addCacheTags($cacheTags);
1706 }
1707 return $content;
1708 }
1709
1710 /**
1711 * setContentToCurrent
1712 * actually it just does the contrary: Sets the value of 'current' based on current content
1713 *
1714 * @param string $content Input value undergoing processing in this function.
1715 * @return string The processed input value
1716 */
1717 public function stdWrap_setContentToCurrent($content = '')
1718 {
1719 $this->data[$this->currentValKey] = $content;
1720 return $content;
1721 }
1722
1723 /**
1724 * setCurrent
1725 * Sets the value of 'current' based on the outcome of stdWrap operations
1726 *
1727 * @param string $content Input value undergoing processing in this function.
1728 * @param array $conf stdWrap properties for setCurrent.
1729 * @return string The processed input value
1730 */
1731 public function stdWrap_setCurrent($content = '', $conf = [])
1732 {
1733 $this->data[$this->currentValKey] = $conf['setCurrent'] ?? null;
1734 return $content;
1735 }
1736
1737 /**
1738 * lang
1739 * Translates content based on the language currently used by the FE
1740 *
1741 * @param string $content Input value undergoing processing in this function.
1742 * @param array $conf stdWrap properties for lang.
1743 * @return string The processed input value
1744 */
1745 public function stdWrap_lang($content = '', $conf = [])
1746 {
1747 $tsfe = $this->getTypoScriptFrontendController();
1748 if (
1749 isset($conf['lang.'])
1750 && isset($tsfe->config['config']['language'])
1751 && $tsfe->config['config']['language']
1752 && isset($conf['lang.'][$tsfe->config['config']['language']])
1753 ) {
1754 $content = $conf['lang.'][$tsfe->config['config']['language']];
1755 }
1756 return $content;
1757 }
1758
1759 /**
1760 * data
1761 * Gets content from different sources based on getText functions, makes use of alternativeData, when set
1762 *
1763 * @param string $content Input value undergoing processing in this function.
1764 * @param array $conf stdWrap properties for data.
1765 * @return string The processed input value
1766 */
1767 public function stdWrap_data($content = '', $conf = [])
1768 {
1769 $content = $this->getData($conf['data'], is_array($this->alternativeData) ? $this->alternativeData : $this->data);
1770 // This must be unset directly after
1771 $this->alternativeData = '';
1772 return $content;
1773 }
1774
1775 /**
1776 * field
1777 * Gets content from a DB field
1778 *
1779 * @param string $content Input value undergoing processing in this function.
1780 * @param array $conf stdWrap properties for field.
1781 * @return string The processed input value
1782 */
1783 public function stdWrap_field($content = '', $conf = [])
1784 {
1785 return $this->getFieldVal($conf['field']);
1786 }
1787
1788 /**
1789 * current
1790 * Gets content that has been perviously set as 'current'
1791 * Can be set via setContentToCurrent or setCurrent or will be set automatically i.e. inside the split function
1792 *
1793 * @param string $content Input value undergoing processing in this function.
1794 * @param array $conf stdWrap properties for current.
1795 * @return string The processed input value
1796 */
1797 public function stdWrap_current($content = '', $conf = [])
1798 {
1799 return $this->data[$this->currentValKey];
1800 }
1801
1802 /**
1803 * cObject
1804 * Will replace the content with the value of an official TypoScript cObject
1805 * like TEXT, COA, HMENU
1806 *
1807 * @param string $content Input value undergoing processing in this function.
1808 * @param array $conf stdWrap properties for cObject.
1809 * @return string The processed input value
1810 */
1811 public function stdWrap_cObject($content = '', $conf = [])
1812 {
1813 return $this->cObjGetSingle($conf['cObject'] ?? '', $conf['cObject.'] ?? [], '/stdWrap/.cObject');
1814 }
1815
1816 /**
1817 * numRows
1818 * Counts the number of returned records of a DB operation
1819 * makes use of select internally
1820 *
1821 * @param string $content Input value undergoing processing in this function.
1822 * @param array $conf stdWrap properties for numRows.
1823 * @return string The processed input value
1824 */
1825 public function stdWrap_numRows($content = '', $conf = [])
1826 {
1827 return $this->numRows($conf['numRows.']);
1828 }
1829
1830 /**
1831 * filelist
1832 * Will create a list of files based on some additional parameters
1833 *
1834 * @param string $content Input value undergoing processing in this function.
1835 * @param array $conf stdWrap properties for filelist.
1836 * @return string The processed input value
1837 */
1838 public function stdWrap_filelist($content = '', $conf = [])
1839 {
1840 return $this->filelist($conf['filelist']);
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 * Copmletely 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 htmlSpecalChars.
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. No quotes are added around the value
2459 * as this can easily be done in TypoScript
2460 *
2461 * @param string $content Input value undergoing processing in this function
2462 * @return string The processed input value
2463 */
2464 public function stdWrap_encodeForJavaScriptValue($content = '')
2465 {
2466 return GeneralUtility::quoteJSvalue($content);
2467 }
2468
2469 /**
2470 * doubleBrTag
2471 * Searches for double line breaks and replaces them with the given value
2472 *
2473 * @param string $content Input value undergoing processing in this function.
2474 * @param array $conf stdWrap properties for doubleBrTag.
2475 * @return string The processed input value
2476 */
2477 public function stdWrap_doubleBrTag($content = '', $conf = [])
2478 {
2479 return preg_replace('/\R{1,2}[\t\x20]*\R{1,2}/', $conf['doubleBrTag'] ?? null, $content);
2480 }
2481
2482 /**
2483 * br
2484 * Searches for single line breaks and replaces them with a <br />/<br> tag
2485 * according to the doctype
2486 *
2487 * @param string $content Input value undergoing processing in this function.
2488 * @return string The processed input value
2489 */
2490 public function stdWrap_br($content = '')
2491 {
2492 return nl2br($content, !empty($this->getTypoScriptFrontendController()->xhtmlDoctype));
2493 }
2494
2495 /**
2496 * brTag
2497 * Searches for single line feeds and replaces them with the given value
2498 *
2499 * @param string $content Input value undergoing processing in this function.
2500 * @param array $conf stdWrap properties for brTag.
2501 * @return string The processed input value
2502 */
2503 public function stdWrap_brTag($content = '', $conf = [])
2504 {
2505 return str_replace(LF, $conf['brTag'] ?? null, $content);
2506 }
2507
2508 /**
2509 * encapsLines
2510 * Modifies text blocks by searching for lines which are not surrounded by HTML tags yet
2511 * and wrapping them with values given by stdWrap properties
2512 *
2513 * @param string $content Input value undergoing processing in this function.
2514 * @param array $conf stdWrap properties for erncapsLines.
2515 * @return string The processed input value
2516 */
2517 public function stdWrap_encapsLines($content = '', $conf = [])
2518 {
2519 return $this->encaps_lineSplit($content, $conf['encapsLines.']);
2520 }
2521
2522 /**
2523 * keywords
2524 * Transforms content into a CSV list to be used i.e. as keywords within a meta tag
2525 *
2526 * @param string $content Input value undergoing processing in this function.
2527 * @return string The processed input value
2528 */
2529 public function stdWrap_keywords($content = '')
2530 {
2531 return $this->keywords($content);
2532 }
2533
2534 /**
2535 * innerWrap
2536 * First of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2537 * See wrap
2538 *
2539 * @param string $content Input value undergoing processing in this function.
2540 * @param array $conf stdWrap properties for innerWrap.
2541 * @return string The processed input value
2542 */
2543 public function stdWrap_innerWrap($content = '', $conf = [])
2544 {
2545 return $this->wrap($content, $conf['innerWrap'] ?? null);
2546 }
2547
2548 /**
2549 * innerWrap2
2550 * Second of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2551 * See wrap
2552 *
2553 * @param string $content Input value undergoing processing in this function.
2554 * @param array $conf stdWrap properties for innerWrap2.
2555 * @return string The processed input value
2556 */
2557 public function stdWrap_innerWrap2($content = '', $conf = [])
2558 {
2559 return $this->wrap($content, $conf['innerWrap2'] ?? null);
2560 }
2561
2562 /**
2563 * addParams
2564 * Adds tag attributes to any content that is a tag
2565 *
2566 * @param string $content Input value undergoing processing in this function.
2567 * @param array $conf stdWrap properties for addParams.
2568 * @return string The processed input value
2569 */
2570 public function stdWrap_addParams($content = '', $conf = [])
2571 {
2572 return $this->addParams($content, $conf['addParams.'] ?? []);
2573 }
2574
2575 /**
2576 * filelink
2577 * Used to make lists of links to files
2578 * See wrap
2579 *
2580 * @param string $content Input value undergoing processing in this function.
2581 * @param array $conf stdWrap properties for filelink.
2582 * @return string The processed input value
2583 */
2584 public function stdWrap_filelink($content = '', $conf = [])
2585 {
2586 return $this->filelink($content, $conf['filelink.'] ?? []);
2587 }
2588
2589 /**
2590 * preCObject
2591 * A content object that is prepended to the current content but between the innerWraps and the rest of the wraps
2592 *
2593 * @param string $content Input value undergoing processing in this function.
2594 * @param array $conf stdWrap properties for preCObject.
2595 * @return string The processed input value
2596 */
2597 public function stdWrap_preCObject($content = '', $conf = [])
2598 {
2599 return $this->cObjGetSingle($conf['preCObject'], $conf['preCObject.'], '/stdWrap/.preCObject') . $content;
2600 }
2601
2602 /**
2603 * postCObject
2604 * A content object that is appended to the current content but between the innerWraps and the rest of the wraps
2605 *
2606 * @param string $content Input value undergoing processing in this function.
2607 * @param array $conf stdWrap properties for postCObject.
2608 * @return string The processed input value
2609 */
2610 public function stdWrap_postCObject($content = '', $conf = [])
2611 {
2612 return $content . $this->cObjGetSingle($conf['postCObject'], $conf['postCObject.'], '/stdWrap/.postCObject');
2613 }
2614
2615 /**
2616 * wrapAlign
2617 * Wraps content with a div container having the style attribute text-align set to the given value
2618 * See wrap
2619 *
2620 * @param string $content Input value undergoing processing in this function.
2621 * @param array $conf stdWrap properties for wrapAlign.
2622 * @return string The processed input value
2623 */
2624 public function stdWrap_wrapAlign($content = '', $conf = [])
2625 {
2626 $wrapAlign = trim($conf['wrapAlign'] ?? '');
2627 if ($wrapAlign) {
2628 $content = $this->wrap($content, '<div style="text-align:' . htmlspecialchars($wrapAlign) . ';">|</div>');
2629 }
2630 return $content;
2631 }
2632
2633 /**
2634 * typolink
2635 * Wraps the content with a link tag
2636 * URLs and other attributes are created automatically by the values given in the stdWrap properties
2637 * See wrap
2638 *
2639 * @param string $content Input value undergoing processing in this function.
2640 * @param array $conf stdWrap properties for typolink.
2641 * @return string The processed input value
2642 */
2643 public function stdWrap_typolink($content = '', $conf = [])
2644 {
2645 return $this->typoLink($content, $conf['typolink.']);
2646 }
2647
2648 /**
2649 * wrap
2650 * This is the "mother" of all wraps
2651 * Third of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2652 * Basically it will put additional content before and after the current content using a split character as a placeholder for the current content
2653 * The default split character is | but it can be replaced with other characters by the property splitChar
2654 * Any other wrap that does not have own splitChar settings will be using the default split char though
2655 *
2656 * @param string $content Input value undergoing processing in this function.
2657 * @param array $conf stdWrap properties for wrap.
2658 * @return string The processed input value
2659 */
2660 public function stdWrap_wrap($content = '', $conf = [])
2661 {
2662 return $this->wrap(
2663 $content,
2664 $conf['wrap'] ?? null,
2665 $conf['wrap.']['splitChar'] ?? '|'
2666 );
2667 }
2668
2669 /**
2670 * noTrimWrap
2671 * Fourth of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2672 * The major difference to any other wrap is, that this one can make use of whitespace without trimming *
2673 *
2674 * @param string $content Input value undergoing processing in this function.
2675 * @param array $conf stdWrap properties for noTrimWrap.
2676 * @return string The processed input value
2677 */
2678 public function stdWrap_noTrimWrap($content = '', $conf = [])
2679 {
2680 $splitChar = isset($conf['noTrimWrap.']['splitChar.'])
2681 ? $this->stdWrap($conf['noTrimWrap.']['splitChar'] ?? '', $conf['noTrimWrap.']['splitChar.'])
2682 : $conf['noTrimWrap.']['splitChar'] ?? '';
2683 if ($splitChar === null || $splitChar === '') {
2684 $splitChar = '|';
2685 }
2686 $content = $this->noTrimWrap(
2687 $content,
2688 $conf['noTrimWrap'],
2689 $splitChar
2690 );
2691 return $content;
2692 }
2693
2694 /**
2695 * wrap2
2696 * Fifth of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2697 * The default split character is | but it can be replaced with other characters by the property splitChar
2698 *
2699 * @param string $content Input value undergoing processing in this function.
2700 * @param array $conf stdWrap properties for wrap2.
2701 * @return string The processed input value
2702 */
2703 public function stdWrap_wrap2($content = '', $conf = [])
2704 {
2705 return $this->wrap(
2706 $content,
2707 $conf['wrap2'] ?? null,
2708 $conf['wrap2.']['splitChar'] ?? '|'
2709 );
2710 }
2711
2712 /**
2713 * dataWrap
2714 * Sixth of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2715 * 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
2716 *
2717 * @param string $content Input value undergoing processing in this function.
2718 * @param array $conf stdWrap properties for dataWrap.
2719 * @return string The processed input value
2720 */
2721 public function stdWrap_dataWrap($content = '', $conf = [])
2722 {
2723 return $this->dataWrap($content, $conf['dataWrap']);
2724 }
2725
2726 /**
2727 * prepend
2728 * A content object that will be prepended to the current content after most of the wraps have already been applied
2729 *
2730 * @param string $content Input value undergoing processing in this function.
2731 * @param array $conf stdWrap properties for prepend.
2732 * @return string The processed input value
2733 */
2734 public function stdWrap_prepend($content = '', $conf = [])
2735 {
2736 return $this->cObjGetSingle($conf['prepend'], $conf['prepend.'], '/stdWrap/.prepend') . $content;
2737 }
2738
2739 /**
2740 * append
2741 * A content object that will be appended to the current content after most of the wraps have already been applied
2742 *
2743 * @param string $content Input value undergoing processing in this function.
2744 * @param array $conf stdWrap properties for append.
2745 * @return string The processed input value
2746 */
2747 public function stdWrap_append($content = '', $conf = [])
2748 {
2749 return $content . $this->cObjGetSingle($conf['append'], $conf['append.'], '/stdWrap/.append');
2750 }
2751
2752 /**
2753 * wrap3
2754 * Seventh of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2755 * The default split character is | but it can be replaced with other characters by the property splitChar
2756 *
2757 * @param string $content Input value undergoing processing in this function.
2758 * @param array $conf stdWrap properties for wrap3.
2759 * @return string The processed input value
2760 */
2761 public function stdWrap_wrap3($content = '', $conf = [])
2762 {
2763 return $this->wrap(
2764 $content,
2765 $conf['wrap3'] ?? null,
2766 $conf['wrap3.']['splitChar'] ?? '|'
2767 );
2768 }
2769
2770 /**
2771 * orderedStdWrap
2772 * Calls stdWrap for each entry in the provided array
2773 *
2774 * @param string $content Input value undergoing processing in this function.
2775 * @param array $conf stdWrap properties for orderedStdWrap.
2776 * @return string The processed input value
2777 */
2778 public function stdWrap_orderedStdWrap($content = '', $conf = [])
2779 {
2780 $sortedKeysArray = ArrayUtility::filterAndSortByNumericKeys($conf['orderedStdWrap.'], true);
2781 foreach ($sortedKeysArray as $key) {
2782 $content = $this->stdWrap($content, $conf['orderedStdWrap.'][$key . '.'] ?? null);
2783 }
2784 return $content;
2785 }
2786
2787 /**
2788 * outerWrap
2789 * Eighth of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2790 *
2791 * @param string $content Input value undergoing processing in this function.
2792 * @param array $conf stdWrap properties for outerWrap.
2793 * @return string The processed input value
2794 */
2795 public function stdWrap_outerWrap($content = '', $conf = [])
2796 {
2797 return $this->wrap($content, $conf['outerWrap'] ?? null);
2798 }
2799
2800 /**
2801 * insertData
2802 * Can fetch additional content the same way data does and replaces any occurrence of {field:whatever} with this content
2803 *
2804 * @param string $content Input value undergoing processing in this function.
2805 * @return string The processed input value
2806 */
2807 public function stdWrap_insertData($content = '')
2808 {
2809 return $this->insertData($content);
2810 }
2811
2812 /**
2813 * postUserFunc
2814 * Will execute a user function after the content has been modified by any other stdWrap function
2815 *
2816 * @param string $content Input value undergoing processing in this function.
2817 * @param array $conf stdWrap properties for postUserFunc.
2818 * @return string The processed input value
2819 */
2820 public function stdWrap_postUserFunc($content = '', $conf = [])
2821 {
2822 return $this->callUserFunction($conf['postUserFunc'], $conf['postUserFunc.'], $content);
2823 }
2824
2825 /**
2826 * postUserFuncInt
2827 * Will execute a user function after the content has been created and each time it is fetched from Cache
2828 * The result of this function itself will not be cached
2829 *
2830 * @param string $content Input value undergoing processing in this function.
2831 * @param array $conf stdWrap properties for postUserFuncInt.
2832 * @return string The processed input value
2833 */
2834 public function stdWrap_postUserFuncInt($content = '', $conf = [])
2835 {
2836 $substKey = 'INT_SCRIPT.' . $this->getTypoScriptFrontendController()->uniqueHash();
2837 $this->getTypoScriptFrontendController()->config['INTincScript'][$substKey] = [
2838 'content' => $content,
2839 'postUserFunc' => $conf['postUserFuncInt'],
2840 'conf' => $conf['postUserFuncInt.'],
2841 'type' => 'POSTUSERFUNC',
2842 'cObj' => serialize($this)
2843 ];
2844 $content = '<!--' . $substKey . '-->';
2845 return $content;
2846 }
2847
2848 /**
2849 * prefixComment
2850 * Will add HTML comments to the content to make it easier to identify certain content elements within the HTML output later on
2851 *
2852 * @param string $content Input value undergoing processing in this function.
2853 * @param array $conf stdWrap properties for prefixComment.
2854 * @return string The processed input value
2855 */
2856 public function stdWrap_prefixComment($content = '', $conf = [])
2857 {
2858 if (
2859 (!isset($this->getTypoScriptFrontendController()->config['config']['disablePrefixComment']) || !$this->getTypoScriptFrontendController()->config['config']['disablePrefixComment'])
2860 && !empty($conf['prefixComment'])
2861 ) {
2862 $content = $this->prefixComment($conf['prefixComment'], [], $content);
2863 }
2864 return $content;
2865 }
2866
2867 /**
2868 * editIcons
2869 * Will render icons for frontend editing as long as there is a BE user logged in
2870 *
2871 * @param string $content Input value undergoing processing in this function.
2872 * @param array $conf stdWrap properties for editIcons.
2873 * @return string The processed input value
2874 */
2875 public function stdWrap_editIcons($content = '', $conf = [])
2876 {
2877 if ($this->getTypoScriptFrontendController()->isBackendUserLoggedIn() && $conf['editIcons']) {
2878 if (!isset($conf['editIcons.']) || !is_array($conf['editIcons.'])) {
2879 $conf['editIcons.'] = [];
2880 }
2881 $content = $this->editIcons($content, $conf['editIcons'], $conf['editIcons.']);
2882 }
2883 return $content;
2884 }
2885
2886 /**
2887 * editPanel
2888 * Will render the edit panel for frontend editing as long as there is a BE user logged in
2889 *
2890 * @param string $content Input value undergoing processing in this function.
2891 * @param array $conf stdWrap properties for editPanel.
2892 * @return string The processed input value
2893 */
2894 public function stdWrap_editPanel($content = '', $conf = [])
2895 {
2896 if ($this->getTypoScriptFrontendController()->isBackendUserLoggedIn()) {
2897 $content = $this->editPanel($content, $conf['editPanel.']);
2898 }
2899 return $content;
2900 }
2901
2902 /**
2903 * Store content into cache
2904 *
2905 * @param string $content Input value undergoing processing in these functions.
2906 * @param array $conf All stdWrap properties, not just the ones for a particular function.
2907 * @return string The processed input value
2908 */
2909 public function stdWrap_cacheStore($content = '', $conf = [])
2910 {
2911 if (!isset($conf['cache.'])) {
2912 return $content;
2913 }
2914 $key = $this->calculateCacheKey($conf['cache.']);
2915 if (empty($key)) {
2916 return $content;
2917 }
2918 /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cacheFrontend */
2919 $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash');
2920 $tags = $this->calculateCacheTags($conf['cache.']);
2921 $lifetime = $this->calculateCacheLifetime($conf['cache.']);
2922 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap_cacheStore'] ?? [] as $_funcRef) {
2923 $params = [
2924 'key' => $key,
2925 'content' => $content,
2926 'lifetime' => $lifetime,
2927 'tags' => $tags
2928 ];
2929 GeneralUtility::callUserFunction($_funcRef, $params, $this);
2930 }
2931 $cacheFrontend->set($key, $content, $tags, $lifetime);
2932 return $content;
2933 }
2934
2935 /**
2936 * stdWrap post process hook
2937 * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
2938 * this hook executes functions at after the content has been modified by the rest of the stdWrap functions but still before debugging
2939 *
2940 * @param string $content Input value undergoing processing in these functions.
2941 * @param array $conf All stdWrap properties, not just the ones for a particular function.
2942 * @return string The processed input value
2943 */
2944 public function stdWrap_stdWrapPostProcess($content = '', $conf = [])
2945 {
2946 foreach ($this->stdWrapHookObjects as $hookObject) {
2947 /** @var ContentObjectStdWrapHookInterface $hookObject */
2948 $content = $hookObject->stdWrapPostProcess