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