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