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