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