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