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