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