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