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