80251ee6fadd096c49ad808bfc9da1dd87859782
[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\Connection;
22 use TYPO3\CMS\Core\Database\ConnectionPool;
23 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
24 use TYPO3\CMS\Core\Database\Query\QueryHelper;
25 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
26 use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
27 use TYPO3\CMS\Core\FrontendEditing\FrontendEditingController;
28 use TYPO3\CMS\Core\Html\HtmlParser;
29 use TYPO3\CMS\Core\LinkHandling\LinkService;
30 use TYPO3\CMS\Core\Log\LogManager;
31 use TYPO3\CMS\Core\Mail\MailMessage;
32 use TYPO3\CMS\Core\Resource\Exception;
33 use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
34 use TYPO3\CMS\Core\Resource\File;
35 use TYPO3\CMS\Core\Resource\FileInterface;
36 use TYPO3\CMS\Core\Resource\FileReference;
37 use TYPO3\CMS\Core\Resource\Folder;
38 use TYPO3\CMS\Core\Resource\ProcessedFile;
39 use TYPO3\CMS\Core\Resource\ResourceFactory;
40 use TYPO3\CMS\Core\Service\DependencyOrderingService;
41 use TYPO3\CMS\Core\Service\MarkerBasedTemplateService;
42 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
43 use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
44 use TYPO3\CMS\Core\Utility\ArrayUtility;
45 use TYPO3\CMS\Core\Utility\DebugUtility;
46 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
47 use TYPO3\CMS\Core\Utility\GeneralUtility;
48 use TYPO3\CMS\Core\Utility\MailUtility;
49 use TYPO3\CMS\Core\Utility\MathUtility;
50 use TYPO3\CMS\Core\Utility\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 * @deprecated since TYPO3 v8, will be removed in TYPO3 v9, use file_get_contents() directly
1499 */
1500 public function fileResource($fName, $addParams = 'alt="" title=""')
1501 {
1502 GeneralUtility::logDeprecatedFunction();
1503 $tsfe = $this->getTypoScriptFrontendController();
1504 $incFile = $tsfe->tmpl->getFileName($fName);
1505 if ($incFile && file_exists($incFile)) {
1506 $fileInfo = GeneralUtility::split_fileref($incFile);
1507 $extension = $fileInfo['fileext'];
1508 if ($extension === 'jpg' || $extension === 'jpeg' || $extension === 'gif' || $extension === 'png') {
1509 $imgFile = $incFile;
1510 $imgInfo = @getimagesize($imgFile);
1511 return '<img src="' . htmlspecialchars($tsfe->absRefPrefix . $imgFile) . '" width="' . (int)$imgInfo[0] . '" height="' . (int)$imgInfo[1] . '"' . $this->getBorderAttr(' border="0"') . ' ' . $addParams . ' />';
1512 } elseif (filesize($incFile) < 1024 * 1024) {
1513 return file_get_contents($incFile);
1514 }
1515 }
1516 return '';
1517 }
1518
1519 /**
1520 * Sets the SYS_LASTCHANGED timestamp if input timestamp is larger than current value.
1521 * The SYS_LASTCHANGED timestamp can be used by various caching/indexing applications to determine if the page has new content.
1522 * Therefore you should call this function with the last-changed timestamp of any element you display.
1523 *
1524 * @param int $tstamp Unix timestamp (number of seconds since 1970)
1525 * @return void
1526 * @see TypoScriptFrontendController::setSysLastChanged()
1527 */
1528 public function lastChanged($tstamp)
1529 {
1530 $tstamp = (int)$tstamp;
1531 $tsfe = $this->getTypoScriptFrontendController();
1532 if ($tstamp > (int)$tsfe->register['SYS_LASTCHANGED']) {
1533 $tsfe->register['SYS_LASTCHANGED'] = $tstamp;
1534 }
1535 }
1536
1537 /**
1538 * Wraps the input string by the $wrap value and implements the "linkWrap" data type as well.
1539 * 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.
1540 *
1541 * @param string $content Input string
1542 * @param string $wrap A string where the first two parts separated by "|" (vertical line) will be wrapped around the input string
1543 * @return string Wrapped output string
1544 * @see wrap(), cImage(), FILE()
1545 */
1546 public function linkWrap($content, $wrap)
1547 {
1548 $wrapArr = explode('|', $wrap);
1549 if (preg_match('/\\{([0-9]*)\\}/', $wrapArr[0], $reg)) {
1550 if ($uid = $this->getTypoScriptFrontendController()->tmpl->rootLine[$reg[1]]['uid']) {
1551 $wrapArr[0] = str_replace($reg[0], $uid, $wrapArr[0]);
1552 }
1553 }
1554 return trim($wrapArr[0]) . $content . trim($wrapArr[1]);
1555 }
1556
1557 /**
1558 * An abstraction method which creates an alt or title parameter for an HTML img, applet, area or input element and the FILE content element.
1559 * From the $conf array it implements the properties "altText", "titleText" and "longdescURL"
1560 *
1561 * @param array $conf TypoScript configuration properties
1562 * @param bool $longDesc If set, the longdesc attribute will be generated - must only be used for img elements!
1563 * @return string Parameter string containing alt and title parameters (if any)
1564 * @see IMGTEXT(), FILE(), FORM(), cImage(), filelink()
1565 */
1566 public function getAltParam($conf, $longDesc = true)
1567 {
1568 $altText = isset($conf['altText.']) ? trim($this->stdWrap($conf['altText'], $conf['altText.'])) : trim($conf['altText']);
1569 $titleText = isset($conf['titleText.']) ? trim($this->stdWrap($conf['titleText'], $conf['titleText.'])) : trim($conf['titleText']);
1570 if (isset($conf['longdescURL.']) && $this->getTypoScriptFrontendController()->config['config']['doctype'] != 'html5') {
1571 $longDescUrl = $this->typoLink_URL($conf['longdescURL.']);
1572 } else {
1573 $longDescUrl = trim($conf['longdescURL']);
1574 }
1575 $longDescUrl = strip_tags($longDescUrl);
1576
1577 // "alt":
1578 $altParam = ' alt="' . htmlspecialchars($altText) . '"';
1579 // "title":
1580 $emptyTitleHandling = isset($conf['emptyTitleHandling.']) ? $this->stdWrap($conf['emptyTitleHandling'], $conf['emptyTitleHandling.']) : $conf['emptyTitleHandling'];
1581 // Choices: 'keepEmpty' | 'useAlt' | 'removeAttr'
1582 if ($titleText || $emptyTitleHandling === 'keepEmpty') {
1583 $altParam .= ' title="' . htmlspecialchars($titleText) . '"';
1584 } elseif (!$titleText && $emptyTitleHandling === 'useAlt') {
1585 $altParam .= ' title="' . htmlspecialchars($altText) . '"';
1586 }
1587 // "longDesc" URL
1588 if ($longDesc && !empty($longDescUrl)) {
1589 $altParam .= ' longdesc="' . htmlspecialchars($longDescUrl) . '"';
1590 }
1591 return $altParam;
1592 }
1593
1594 /**
1595 * An abstraction method to add parameters to an A tag.
1596 * Uses the ATagParams property.
1597 *
1598 * @param array $conf TypoScript configuration properties
1599 * @param bool|int $addGlobal If set, will add the global config.ATagParams to the link
1600 * @return string String containing the parameters to the A tag (if non empty, with a leading space)
1601 * @see IMGTEXT(), filelink(), makelinks(), typolink()
1602 */
1603 public function getATagParams($conf, $addGlobal = 1)
1604 {
1605 $aTagParams = '';
1606 if ($conf['ATagParams.']) {
1607 $aTagParams = ' ' . $this->stdWrap($conf['ATagParams'], $conf['ATagParams.']);
1608 } elseif ($conf['ATagParams']) {
1609 $aTagParams = ' ' . $conf['ATagParams'];
1610 }
1611 if ($addGlobal) {
1612 $aTagParams = ' ' . trim($this->getTypoScriptFrontendController()->ATagParams . $aTagParams);
1613 }
1614 // Extend params
1615 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'])) {
1616 $_params = [
1617 'conf' => &$conf,
1618 'aTagParams' => &$aTagParams
1619 ];
1620 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getATagParamsPostProc'] as $objRef) {
1621 $processor =& GeneralUtility::getUserObj($objRef);
1622 $aTagParams = $processor->process($_params, $this);
1623 }
1624 }
1625
1626 $aTagParams = trim($aTagParams);
1627 if (!empty($aTagParams)) {
1628 $aTagParams = ' ' . $aTagParams;
1629 }
1630
1631 return $aTagParams;
1632 }
1633
1634 /**
1635 * All extension links should ask this function for additional properties to their tags.
1636 * Designed to add for instance an "onclick" property for site tracking systems.
1637 *
1638 * @param string $URL URL of the website
1639 * @param string $TYPE
1640 * @return string The additional tag properties
1641 */
1642 public function extLinkATagParams($URL, $TYPE)
1643 {
1644 $out = '';
1645 if ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['extLinkATagParamsHandler']) {
1646 $extLinkATagParamsHandler = GeneralUtility::getUserObj($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['extLinkATagParamsHandler']);
1647 if (method_exists($extLinkATagParamsHandler, 'main')) {
1648 $out .= trim($extLinkATagParamsHandler->main($URL, $TYPE, $this));
1649 }
1650 }
1651 return trim($out) ? ' ' . trim($out) : '';
1652 }
1653
1654 /***********************************************
1655 *
1656 * HTML template processing functions
1657 *
1658 ***********************************************/
1659 /**
1660 * Returns a subpart from the input content stream.
1661 * A subpart is a part of the input stream which is encapsulated in a
1662 * string matching the input string, $marker. If this string is found
1663 * inside of HTML comment tags the start/end points of the content block
1664 * returned will be that right outside that comment block.
1665 * Example: The contennt string is
1666 * "Hello <!--###sub1### begin--> World. How are <!--###sub1### end--> you?"
1667 * If $marker is "###sub1###" then the content returned is
1668 * " World. How are ". The input content string could just as well have
1669 * been "Hello ###sub1### World. How are ###sub1### you?" and the result
1670 * would be the same
1671 * Wrapper for \TYPO3\CMS\Core\Utility\MarkerBasedTemplateService::getSubpart which behaves identical
1672 *
1673 * @param string $content The content stream, typically HTML template content.
1674 * @param string $marker The marker string, typically on the form "###[the marker string]###
1675 * @return string The subpart found, if found.
1676 */
1677 public function getSubpart($content, $marker)
1678 {
1679 return $this->templateService->getSubpart($content, $marker);
1680 }
1681
1682 /**
1683 * Substitute subpart in input template stream.
1684 * This function substitutes a subpart in $content with the content of
1685 * $subpartContent.
1686 * Wrapper for \TYPO3\CMS\Core\Utility\MarkerBasedTemplateService::substituteSubpart which behaves identical
1687 *
1688 * @param string $content The content stream, typically HTML template content.
1689 * @param string $marker The marker string, typically on the form "###[the marker string]###
1690 * @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.
1691 * @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!
1692 * @return string The processed HTML content string.
1693 */
1694 public function substituteSubpart($content, $marker, $subpartContent, $recursive = 1)
1695 {
1696 return $this->templateService->substituteSubpart($content, $marker, $subpartContent, $recursive);
1697 }
1698
1699 /**
1700 * Substitues multiple subparts at once
1701 *
1702 * @param string $content The content stream, typically HTML template content.
1703 * @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.
1704 * @return string The processed HTML content string.
1705 */
1706 public function substituteSubpartArray($content, array $subpartsContent)
1707 {
1708 return $this->templateService->substituteSubpartArray($content, $subpartsContent);
1709 }
1710
1711 /**
1712 * Substitutes a marker string in the input content
1713 * (by a simple str_replace())
1714 *
1715 * @param string $content The content stream, typically HTML template content.
1716 * @param string $marker The marker string, typically on the form "###[the marker string]###
1717 * @param mixed $markContent The content to insert instead of the marker string found.
1718 * @return string The processed HTML content string.
1719 * @see substituteSubpart()
1720 */
1721 public function substituteMarker($content, $marker, $markContent)
1722 {
1723 return $this->templateService->substituteMarker($content, $marker, $markContent);
1724 }
1725
1726 /**
1727 * Multi substitution function with caching.
1728 *
1729 * This function should be a one-stop substitution function for working
1730 * with HTML-template. It does not substitute by str_replace but by
1731 * splitting. This secures that the value inserted does not themselves
1732 * contain markers or subparts.
1733 *
1734 * Note that the "caching" won't cache the content of the substition,
1735 * but only the splitting of the template in various parts. So if you
1736 * want only one cache-entry per template, make sure you always pass the
1737 * exact same set of marker/subpart keys. Else you will be flooding the
1738 * user's cache table.
1739 *
1740 * This function takes three kinds of substitutions in one:
1741 * $markContentArray is a regular marker-array where the 'keys' are
1742 * substituted in $content with their values
1743 *
1744 * $subpartContentArray works exactly like markContentArray only is whole
1745 * subparts substituted and not only a single marker.
1746 *
1747 * $wrappedSubpartContentArray is an array of arrays with 0/1 keys where
1748 * the subparts pointed to by the main key is wrapped with the 0/1 value
1749 * alternating.
1750 *
1751 * @param string $content The content stream, typically HTML template content.
1752 * @param array $markContentArray Regular marker-array where the 'keys' are substituted in $content with their values
1753 * @param array $subpartContentArray Exactly like markContentArray only is whole subparts substituted and not only a single marker.
1754 * @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.
1755 * @return string The output content stream
1756 * @see substituteSubpart(), substituteMarker(), substituteMarkerInObject(), TEMPLATE()
1757 */
1758 public function substituteMarkerArrayCached($content, array $markContentArray = null, array $subpartContentArray = null, array $wrappedSubpartContentArray = null)
1759 {
1760 $timeTracker = $this->getTimeTracker();
1761 $timeTracker->push('substituteMarkerArrayCached');
1762 // If not arrays then set them
1763 if (is_null($markContentArray)) {
1764 // Plain markers
1765 $markContentArray = [];
1766 }
1767 if (is_null($subpartContentArray)) {
1768 // Subparts being directly substituted
1769 $subpartContentArray = [];
1770 }
1771 if (is_null($wrappedSubpartContentArray)) {
1772 // Subparts being wrapped
1773 $wrappedSubpartContentArray = [];
1774 }
1775 // Finding keys and check hash:
1776 $sPkeys = array_keys($subpartContentArray);
1777 $wPkeys = array_keys($wrappedSubpartContentArray);
1778 $keysToReplace = array_merge(array_keys($markContentArray), $sPkeys, $wPkeys);
1779 if (empty($keysToReplace)) {
1780 $timeTracker->pull();
1781 return $content;
1782 }
1783 asort($keysToReplace);
1784 $storeKey = md5('substituteMarkerArrayCached_storeKey:' . serialize([$content, $keysToReplace]));
1785 if ($this->substMarkerCache[$storeKey]) {
1786 $storeArr = $this->substMarkerCache[$storeKey];
1787 $timeTracker->setTSlogMessage('Cached', 0);
1788 } else {
1789 $storeArrDat = $this->getTypoScriptFrontendController()->sys_page->getHash($storeKey);
1790 if (is_array($storeArrDat)) {
1791 $storeArr = $storeArrDat;
1792 // Setting cache:
1793 $this->substMarkerCache[$storeKey] = $storeArr;
1794 $timeTracker->setTSlogMessage('Cached from DB', 0);
1795 } else {
1796 // Finding subparts and substituting them with the subpart as a marker
1797 foreach ($sPkeys as $sPK) {
1798 $content = $this->substituteSubpart($content, $sPK, $sPK);
1799 }
1800 // Finding subparts and wrapping them with markers
1801 foreach ($wPkeys as $wPK) {
1802 $content = $this->substituteSubpart($content, $wPK, [
1803 $wPK,
1804 $wPK
1805 ]);
1806 }
1807
1808 $storeArr = [];
1809 // search all markers in the content
1810 $result = preg_match_all('/###([^#](?:[^#]*+|#{1,2}[^#])+)###/', $content, $markersInContent);
1811 if ($result !== false && !empty($markersInContent[1])) {
1812 $keysToReplaceFlipped = array_flip($keysToReplace);
1813 $regexKeys = [];
1814 $wrappedKeys = [];
1815 // Traverse keys and quote them for reg ex.
1816 foreach ($markersInContent[1] as $key) {
1817 if (isset($keysToReplaceFlipped['###' . $key . '###'])) {
1818 $regexKeys[] = preg_quote($key, '/');
1819 $wrappedKeys[] = '###' . $key . '###';
1820 }
1821 }
1822 $regex = '/###(?:' . implode('|', $regexKeys) . ')###/';
1823 $storeArr['c'] = preg_split($regex, $content); // contains all content parts around markers
1824 $storeArr['k'] = $wrappedKeys; // contains all markers incl. ###
1825 // Setting cache:
1826 $this->substMarkerCache[$storeKey] = $storeArr;
1827 // Storing the cached data:
1828 $this->getTypoScriptFrontendController()->sys_page->storeHash($storeKey, $storeArr, 'substMarkArrayCached');
1829 }
1830 $timeTracker->setTSlogMessage('Parsing', 0);
1831 }
1832 }
1833 if (!empty($storeArr['k']) && is_array($storeArr['k'])) {
1834 // Substitution/Merging:
1835 // Merging content types together, resetting
1836 $valueArr = array_merge($markContentArray, $subpartContentArray, $wrappedSubpartContentArray);
1837 $wSCA_reg = [];
1838 $content = '';
1839 // Traversing the keyList array and merging the static and dynamic content
1840 foreach ($storeArr['k'] as $n => $keyN) {
1841 // add content before marker
1842 $content .= $storeArr['c'][$n];
1843 if (!is_array($valueArr[$keyN])) {
1844 // fetch marker replacement from $markContentArray or $subpartContentArray
1845 $content .= $valueArr[$keyN];
1846 } else {
1847 if (!isset($wSCA_reg[$keyN])) {
1848 $wSCA_reg[$keyN] = 0;
1849 }
1850 // fetch marker replacement from $wrappedSubpartContentArray
1851 $content .= $valueArr[$keyN][$wSCA_reg[$keyN] % 2];
1852 $wSCA_reg[$keyN]++;
1853 }
1854 }
1855 // add remaining content
1856 $content .= $storeArr['c'][count($storeArr['k'])];
1857 }
1858 $timeTracker->pull();
1859 return $content;
1860 }
1861
1862 /**
1863 * Traverses the input $markContentArray array and for each key the marker
1864 * by the same name (possibly wrapped and in upper case) will be
1865 * substituted with the keys value in the array.
1866 *
1867 * This is very useful if you have a data-record to substitute in some
1868 * content. In particular when you use the $wrap and $uppercase values to
1869 * pre-process the markers. Eg. a key name like "myfield" could effectively
1870 * be represented by the marker "###MYFIELD###" if the wrap value
1871 * was "###|###" and the $uppercase boolean TRUE.
1872 *
1873 * @param string $content The content stream, typically HTML template content.
1874 * @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.
1875 * @param string $wrap A wrap value - [part 1] | [part 2] - for the markers before substitution
1876 * @param bool $uppercase If set, all marker string substitution is done with upper-case markers.
1877 * @param bool $deleteUnused If set, all unused marker are deleted.
1878 * @return string The processed output stream
1879 * @see substituteMarker(), substituteMarkerInObject(), TEMPLATE()
1880 */
1881 public function substituteMarkerArray($content, array $markContentArray, $wrap = '', $uppercase = false, $deleteUnused = false)
1882 {
1883 return $this->templateService->substituteMarkerArray($content, $markContentArray, $wrap, $uppercase, $deleteUnused);
1884 }
1885
1886 /**
1887 * Substitute marker array in an array of values
1888 *
1889 * @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)
1890 * @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.
1891 * @return mixed The processed input variable.
1892 * @see substituteMarker()
1893 */
1894 public function substituteMarkerInObject(&$tree, array $markContentArray)
1895 {
1896 if (is_array($tree)) {
1897 foreach ($tree as $key => $value) {
1898 $this->substituteMarkerInObject($tree[$key], $markContentArray);
1899 }
1900 } else {
1901 $tree = $this->substituteMarkerArray($tree, $markContentArray);
1902 }
1903 return $tree;
1904 }
1905
1906 /**
1907 * Replaces all markers and subparts in a template with the content provided in the structured array.
1908 *
1909 * @param string $content
1910 * @param array $markersAndSubparts
1911 * @param string $wrap
1912 * @param bool $uppercase
1913 * @param bool $deleteUnused
1914 * @return string
1915 */
1916 public function substituteMarkerAndSubpartArrayRecursive($content, array $markersAndSubparts, $wrap = '', $uppercase = false, $deleteUnused = false)
1917 {
1918 return $this->templateService->substituteMarkerAndSubpartArrayRecursive($content, $markersAndSubparts, $wrap, $uppercase, $deleteUnused);
1919 }
1920
1921 /**
1922 * Adds elements to the input $markContentArray based on the values from
1923 * the fields from $fieldList found in $row
1924 *
1925 * @param array $markContentArray Array with key/values being marker-strings/substitution values.
1926 * @param array $row An array with keys found in the $fieldList (typically a record) which values should be moved to the $markContentArray
1927 * @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)
1928 * @param bool $nl2br If set, all values added to $markContentArray will be nl2br()'ed
1929 * @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 "###
1930 * @param bool $HSC If set, all values are passed through htmlspecialchars() - RECOMMENDED to avoid most obvious XSS and maintain XHTML compliance.
1931 * @return array The modified $markContentArray
1932 */
1933 public function fillInMarkerArray(array $markContentArray, array $row, $fieldList = '', $nl2br = true, $prefix = 'FIELD_', $HSC = false)
1934 {
1935 $tsfe = $this->getTypoScriptFrontendController();
1936 if ($fieldList) {
1937 $fArr = GeneralUtility::trimExplode(',', $fieldList, true);
1938 foreach ($fArr as $field) {
1939 $markContentArray['###' . $prefix . $field . '###'] = $nl2br ? nl2br($row[$field], !empty($tsfe->xhtmlDoctype)) : $row[$field];
1940 }
1941 } else {
1942 if (is_array($row)) {
1943 foreach ($row as $field => $value) {
1944 if (!MathUtility::canBeInterpretedAsInteger($field)) {
1945 if ($HSC) {
1946 $value = htmlspecialchars($value);
1947 }
1948 $markContentArray['###' . $prefix . $field . '###'] = $nl2br ? nl2br($value, !empty($tsfe->xhtmlDoctype)) : $value;
1949 }
1950 }
1951 }
1952 }
1953 return $markContentArray;
1954 }
1955
1956 /**
1957 * Sets the current file object during iterations over files.
1958 *
1959 * @param File $fileObject The file object.
1960 */
1961 public function setCurrentFile($fileObject)
1962 {
1963 $this->currentFile = $fileObject;
1964 }
1965
1966 /**
1967 * Gets the current file object during iterations over files.
1968 *
1969 * @return File The current file object.
1970 */
1971 public function getCurrentFile()
1972 {
1973 return $this->currentFile;
1974 }
1975
1976 /***********************************************
1977 *
1978 * "stdWrap" + sub functions
1979 *
1980 ***********************************************/
1981 /**
1982 * The "stdWrap" function. This is the implementation of what is known as "stdWrap properties" in TypoScript.
1983 * Basically "stdWrap" performs some processing of a value based on properties in the input $conf array(holding the TypoScript "stdWrap properties")
1984 * 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.
1985 *
1986 * If $this->alternativeData is an array it's used instead of the $this->data array in ->getData
1987 *
1988 * @param string $content Input value undergoing processing in this function. Possibly substituted by other values fetched from another source.
1989 * @param array $conf TypoScript "stdWrap properties".
1990 * @return string The processed input value
1991 */
1992 public function stdWrap($content = '', $conf = [])
1993 {
1994 $content = (string)$content;
1995 // If there is any hook object, activate all of the process and override functions.
1996 // The hook interface ContentObjectStdWrapHookInterface takes care that all 4 methods exist.
1997 if ($this->stdWrapHookObjects) {
1998 $conf['stdWrapPreProcess'] = 1;
1999 $conf['stdWrapOverride'] = 1;
2000 $conf['stdWrapProcess'] = 1;
2001 $conf['stdWrapPostProcess'] = 1;
2002 }
2003
2004 if (!is_array($conf) || !$conf) {
2005 return $content;
2006 }
2007
2008 // Cache handling
2009 if (is_array($conf['cache.'])) {
2010 $conf['cache.']['key'] = $this->stdWrap($conf['cache.']['key'], $conf['cache.']['key.']);
2011 $conf['cache.']['tags'] = $this->stdWrap($conf['cache.']['tags'], $conf['cache.']['tags.']);
2012 $conf['cache.']['lifetime'] = $this->stdWrap($conf['cache.']['lifetime'], $conf['cache.']['lifetime.']);
2013 $conf['cacheRead'] = 1;
2014 $conf['cacheStore'] = 1;
2015 }
2016 // The configuration is sorted and filtered by intersection with the defined stdWrapOrder.
2017 $sortedConf = array_keys(array_intersect_key($this->stdWrapOrder, $conf));
2018 // Functions types that should not make use of nested stdWrap function calls to avoid conflicts with internal TypoScript used by these functions
2019 $stdWrapDisabledFunctionTypes = 'cObject,functionName,stdWrap';
2020 // Additional Array to check whether a function has already been executed
2021 $isExecuted = [];
2022 // Additional switch to make sure 'required', 'if' and 'fieldRequired'
2023 // will still stop rendering immediately in case they return FALSE
2024 $this->stdWrapRecursionLevel++;
2025 $this->stopRendering[$this->stdWrapRecursionLevel] = false;
2026 // execute each function in the predefined order
2027 foreach ($sortedConf as $stdWrapName) {
2028 // eliminate the second key of a pair 'key'|'key.' to make sure functions get called only once and check if rendering has been stopped
2029 if (!$isExecuted[$stdWrapName] && !$this->stopRendering[$this->stdWrapRecursionLevel]) {
2030 $functionName = rtrim($stdWrapName, '.');
2031 $functionProperties = $functionName . '.';
2032 $functionType = $this->stdWrapOrder[$functionName];
2033 // If there is any code on the next level, check if it contains "official" stdWrap functions
2034 // if yes, execute them first - will make each function stdWrap aware
2035 // so additional stdWrap calls within the functions can be removed, since the result will be the same
2036 if (!empty($conf[$functionProperties]) && !GeneralUtility::inList($stdWrapDisabledFunctionTypes, $functionType)) {
2037 if (array_intersect_key($this->stdWrapOrder, $conf[$functionProperties])) {
2038 $conf[$functionName] = $this->stdWrap($conf[$functionName], $conf[$functionProperties]);
2039 }
2040 }
2041 // Check if key is still containing something, since it might have been changed by next level stdWrap before
2042 if ((isset($conf[$functionName]) || $conf[$functionProperties]) && ($functionType !== 'boolean' || $conf[$functionName])) {
2043 // Get just that part of $conf that is needed for the particular function
2044 $singleConf = [
2045 $functionName => $conf[$functionName],
2046 $functionProperties => $conf[$functionProperties]
2047 ];
2048 // In this special case 'spaceBefore' and 'spaceAfter' need additional stuff from 'space.''
2049 if ($functionName === 'spaceBefore' || $functionName === 'spaceAfter') {
2050 $singleConf['space.'] = $conf['space.'];
2051 }
2052 // Hand over the whole $conf array to the stdWrapHookObjects
2053 if ($functionType === 'hook') {
2054 $singleConf = $conf;
2055 }
2056 // Add both keys - with and without the dot - to the set of executed functions
2057 $isExecuted[$functionName] = true;
2058 $isExecuted[$functionProperties] = true;
2059 // Call the function with the prefix stdWrap_ to make sure nobody can execute functions just by adding their name to the TS Array
2060 $functionName = 'stdWrap_' . $functionName;
2061 $content = $this->{$functionName}($content, $singleConf);
2062 } elseif ($functionType === 'boolean' && !$conf[$functionName]) {
2063 $isExecuted[$functionName] = true;
2064 $isExecuted[$functionProperties] = true;
2065 }
2066 }
2067 }
2068 unset($this->stopRendering[$this->stdWrapRecursionLevel]);
2069 $this->stdWrapRecursionLevel--;
2070
2071 return $content;
2072 }
2073
2074 /**
2075 * Gets a configuration value by passing them through stdWrap first and taking a default value if stdWrap doesn't yield a result.
2076 *
2077 * @param string $key The config variable key (from TS array).
2078 * @param array $config The TypoScript array.
2079 * @param string $defaultValue Optional default value.
2080 * @return string Value of the config variable
2081 */
2082 public function stdWrapValue($key, array $config, $defaultValue = '')
2083 {
2084 if (isset($config[$key])) {
2085 if (!isset($config[$key . '.'])) {
2086 return $config[$key];
2087 }
2088 } elseif (isset($config[$key . '.'])) {
2089 $config[$key] = '';
2090 } else {
2091 return $defaultValue;
2092 }
2093 $stdWrapped = $this->stdWrap($config[$key], $config[$key . '.']);
2094 return $stdWrapped ?: $defaultValue;
2095 }
2096
2097 /**
2098 * stdWrap pre process hook
2099 * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
2100 * this hook will execute functions before any other stdWrap function can modify anything
2101 *
2102 * @param string $content Input value undergoing processing in these functions.
2103 * @param array $conf All stdWrap properties, not just the ones for a particular function.
2104 * @return string The processed input value
2105 */
2106 public function stdWrap_stdWrapPreProcess($content = '', $conf = [])
2107 {
2108 foreach ($this->stdWrapHookObjects as $hookObject) {
2109 /** @var ContentObjectStdWrapHookInterface $hookObject */
2110 $content = $hookObject->stdWrapPreProcess($content, $conf, $this);
2111 }
2112 return $content;
2113 }
2114
2115 /**
2116 * Check if content was cached before (depending on the given cache key)
2117 *
2118 * @param string $content Input value undergoing processing in these functions.
2119 * @param array $conf All stdWrap properties, not just the ones for a particular function.
2120 * @return string The processed input value
2121 */
2122 public function stdWrap_cacheRead($content = '', $conf = [])
2123 {
2124 if (!isset($conf['cache.'])) {
2125 return $content;
2126 }
2127 $result = $this->getFromCache($conf['cache.']);
2128 return $result === false ? $content : $result;
2129 }
2130
2131 /**
2132 * Add tags to page cache (comma-separated list)
2133 *
2134 * @param string $content Input value undergoing processing in these functions.
2135 * @param array $conf All stdWrap properties, not just the ones for a particular function.
2136 * @return string The processed input value
2137 */
2138 public function stdWrap_addPageCacheTags($content = '', $conf = [])
2139 {
2140 $tags = isset($conf['addPageCacheTags.'])
2141 ? $this->stdWrap($conf['addPageCacheTags'], $conf['addPageCacheTags.'])
2142 : $conf['addPageCacheTags'];
2143 if (!empty($tags)) {
2144 $cacheTags = GeneralUtility::trimExplode(',', $tags, true);
2145 $this->getTypoScriptFrontendController()->addCacheTags($cacheTags);
2146 }
2147 return $content;
2148 }
2149
2150 /**
2151 * setContentToCurrent
2152 * actually it just does the contrary: Sets the value of 'current' based on current content
2153 *
2154 * @param string $content Input value undergoing processing in this function.
2155 * @return string The processed input value
2156 */
2157 public function stdWrap_setContentToCurrent($content = '')
2158 {
2159 $this->data[$this->currentValKey] = $content;
2160 return $content;
2161 }
2162
2163 /**
2164 * setCurrent
2165 * Sets the value of 'current' based on the outcome of stdWrap operations
2166 *
2167 * @param string $content Input value undergoing processing in this function.
2168 * @param array $conf stdWrap properties for setCurrent.
2169 * @return string The processed input value
2170 */
2171 public function stdWrap_setCurrent($content = '', $conf = [])
2172 {
2173 $this->data[$this->currentValKey] = $conf['setCurrent'];
2174 return $content;
2175 }
2176
2177 /**
2178 * lang
2179 * Translates content based on the language currently used by the FE
2180 *
2181 * @param string $content Input value undergoing processing in this function.
2182 * @param array $conf stdWrap properties for lang.
2183 * @return string The processed input value
2184 */
2185 public function stdWrap_lang($content = '', $conf = [])
2186 {
2187 $tsfe = $this->getTypoScriptFrontendController();
2188 if (isset($conf['lang.']) && $tsfe->config['config']['language'] && isset($conf['lang.'][$tsfe->config['config']['language']])) {
2189 $content = $conf['lang.'][$tsfe->config['config']['language']];
2190 }
2191 return $content;
2192 }
2193
2194 /**
2195 * data
2196 * Gets content from different sources based on getText functions, makes use of alternativeData, when set
2197 *
2198 * @param string $content Input value undergoing processing in this function.
2199 * @param array $conf stdWrap properties for data.
2200 * @return string The processed input value
2201 */
2202 public function stdWrap_data($content = '', $conf = [])
2203 {
2204 $content = $this->getData($conf['data'], is_array($this->alternativeData) ? $this->alternativeData : $this->data);
2205 // This must be unset directly after
2206 $this->alternativeData = '';
2207 return $content;
2208 }
2209
2210 /**
2211 * field
2212 * Gets content from a DB field
2213 *
2214 * @param string $content Input value undergoing processing in this function.
2215 * @param array $conf stdWrap properties for field.
2216 * @return string The processed input value
2217 */
2218 public function stdWrap_field($content = '', $conf = [])
2219 {
2220 return $this->getFieldVal($conf['field']);
2221 }
2222
2223 /**
2224 * current
2225 * Gets content that has been perviously set as 'current'
2226 * Can be set via setContentToCurrent or setCurrent or will be set automatically i.e. inside the split function
2227 *
2228 * @param string $content Input value undergoing processing in this function.
2229 * @param array $conf stdWrap properties for current.
2230 * @return string The processed input value
2231 */
2232 public function stdWrap_current($content = '', $conf = [])
2233 {
2234 return $this->data[$this->currentValKey];
2235 }
2236
2237 /**
2238 * cObject
2239 * Will replace the content with the value of an official TypoScript cObject
2240 * like TEXT, COA, HMENU
2241 *
2242 * @param string $content Input value undergoing processing in this function.
2243 * @param array $conf stdWrap properties for cObject.
2244 * @return string The processed input value
2245 */
2246 public function stdWrap_cObject($content = '', $conf = [])
2247 {
2248 return $this->cObjGetSingle($conf['cObject'], $conf['cObject.'], '/stdWrap/.cObject');
2249 }
2250
2251 /**
2252 * numRows
2253 * Counts the number of returned records of a DB operation
2254 * makes use of select internally
2255 *
2256 * @param string $content Input value undergoing processing in this function.
2257 * @param array $conf stdWrap properties for numRows.
2258 * @return string The processed input value
2259 */
2260 public function stdWrap_numRows($content = '', $conf = [])
2261 {
2262 return $this->numRows($conf['numRows.']);
2263 }
2264
2265 /**
2266 * filelist
2267 * Will create a list of files based on some additional parameters
2268 *
2269 * @param string $content Input value undergoing processing in this function.
2270 * @param array $conf stdWrap properties for filelist.
2271 * @return string The processed input value
2272 */
2273 public function stdWrap_filelist($content = '', $conf = [])
2274 {
2275 return $this->filelist($conf['filelist']);
2276 }
2277
2278 /**
2279 * preUserFunc
2280 * Will execute a user public function before the content will be modified by any other stdWrap function
2281 *
2282 * @param string $content Input value undergoing processing in this function.
2283 * @param array $conf stdWrap properties for preUserFunc.
2284 * @return string The processed input value
2285 */
2286 public function stdWrap_preUserFunc($content = '', $conf = [])
2287 {
2288 return $this->callUserFunction($conf['preUserFunc'], $conf['preUserFunc.'], $content);
2289 }
2290
2291 /**
2292 * stdWrap override hook
2293 * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
2294 * this hook will execute functions on existing content but still before the content gets modified or replaced
2295 *
2296 * @param string $content Input value undergoing processing in these functions.
2297 * @param array $conf All stdWrap properties, not just the ones for a particular function.
2298 * @return string The processed input value
2299 */
2300 public function stdWrap_stdWrapOverride($content = '', $conf = [])
2301 {
2302 foreach ($this->stdWrapHookObjects as $hookObject) {
2303 /** @var ContentObjectStdWrapHookInterface $hookObject */
2304 $content = $hookObject->stdWrapOverride($content, $conf, $this);
2305 }
2306 return $content;
2307 }
2308
2309 /**
2310 * override
2311 * Will override the current value of content with its own value'
2312 *
2313 * @param string $content Input value undergoing processing in this function.
2314 * @param array $conf stdWrap properties for override.
2315 * @return string The processed input value
2316 */
2317 public function stdWrap_override($content = '', $conf = [])
2318 {
2319 if (trim($conf['override'])) {
2320 $content = $conf['override'];
2321 }
2322 return $content;
2323 }
2324
2325 /**
2326 * preIfEmptyListNum
2327 * Gets a value off a CSV list before the following ifEmpty check
2328 * 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
2329 *
2330 * @param string $content Input value undergoing processing in this function.
2331 * @param array $conf stdWrap properties for preIfEmptyListNum.
2332 * @return string The processed input value
2333 */
2334 public function stdWrap_preIfEmptyListNum($content = '', $conf = [])
2335 {
2336 return $this->listNum($content, $conf['preIfEmptyListNum'], $conf['preIfEmptyListNum.']['splitChar']);
2337 }
2338
2339 /**
2340 * ifNull
2341 * Will set content to a replacement value in case the value of content is NULL
2342 *
2343 * @param string|NULL $content Input value undergoing processing in this function.
2344 * @param array $conf stdWrap properties for ifNull.
2345 * @return string The processed input value
2346 */
2347 public function stdWrap_ifNull($content = '', $conf = [])
2348 {
2349 return $content !== null ? $content : $conf['ifNull'];
2350 }
2351
2352 /**
2353 * ifEmpty
2354 * Will set content to a replacement value in case the trimmed value of content returns FALSE
2355 * 0 (zero) will be replaced as well
2356 *
2357 * @param string $content Input value undergoing processing in this function.
2358 * @param array $conf stdWrap properties for ifEmpty.
2359 * @return string The processed input value
2360 */
2361 public function stdWrap_ifEmpty($content = '', $conf = [])
2362 {
2363 if (!trim($content)) {
2364 $content = $conf['ifEmpty'];
2365 }
2366 return $content;
2367 }
2368
2369 /**
2370 * ifBlank
2371 * Will set content to a replacement value in case the trimmed value of content has no length
2372 * 0 (zero) will not be replaced
2373 *
2374 * @param string $content Input value undergoing processing in this function.
2375 * @param array $conf stdWrap properties for ifBlank.
2376 * @return string The processed input value
2377 */
2378 public function stdWrap_ifBlank($content = '', $conf = [])
2379 {
2380 if (trim($content) === '') {
2381 $content = $conf['ifBlank'];
2382 }
2383 return $content;
2384 }
2385
2386 /**
2387 * listNum
2388 * Gets a value off a CSV list after ifEmpty check
2389 * Might return an empty value in case the CSV does not contain a value at the position given by listNum
2390 * Use preIfEmptyListNum to avoid that behaviour
2391 *
2392 * @param string $content Input value undergoing processing in this function.
2393 * @param array $conf stdWrap properties for listNum.
2394 * @return string The processed input value
2395 */
2396 public function stdWrap_listNum($content = '', $conf = [])
2397 {
2398 return $this->listNum($content, $conf['listNum'], $conf['listNum.']['splitChar']);
2399 }
2400
2401 /**
2402 * trim
2403 * Cuts off any whitespace at the beginning and the end of the content
2404 *
2405 * @param string $content Input value undergoing processing in this function.
2406 * @return string The processed input value
2407 */
2408 public function stdWrap_trim($content = '')
2409 {
2410 return trim($content);
2411 }
2412
2413 /**
2414 * strPad
2415 * Will return a string padded left/right/on both sides, based on configuration given as stdWrap properties
2416 *
2417 * @param string $content Input value undergoing processing in this function.
2418 * @param array $conf stdWrap properties for strPad.
2419 * @return string The processed input value
2420 */
2421 public function stdWrap_strPad($content = '', $conf = [])
2422 {
2423 // Must specify a length in conf for this to make sense
2424 $length = 0;
2425 // Padding with space is PHP-default
2426 $padWith = ' ';
2427 // Padding on the right side is PHP-default
2428 $padType = STR_PAD_RIGHT;
2429 if (!empty($conf['strPad.']['length'])) {
2430 $length = isset($conf['strPad.']['length.']) ? $this->stdWrap($conf['strPad.']['length'], $conf['strPad.']['length.']) : $conf['strPad.']['length'];
2431 $length = (int)$length;
2432 }
2433 if (isset($conf['strPad.']['padWith']) && (string)$conf['strPad.']['padWith'] !== '') {
2434 $padWith = isset($conf['strPad.']['padWith.']) ? $this->stdWrap($conf['strPad.']['padWith'], $conf['strPad.']['padWith.']) : $conf['strPad.']['padWith'];
2435 }
2436 if (!empty($conf['strPad.']['type'])) {
2437 $type = isset($conf['strPad.']['type.']) ? $this->stdWrap($conf['strPad.']['type'], $conf['strPad.']['type.']) : $conf['strPad.']['type'];
2438 if (strtolower($type) === 'left') {
2439 $padType = STR_PAD_LEFT;
2440 } elseif (strtolower($type) === 'both') {
2441 $padType = STR_PAD_BOTH;
2442 }
2443 }
2444 return str_pad($content, $length, $padWith, $padType);
2445 }
2446
2447 /**
2448 * stdWrap
2449 * A recursive call of the stdWrap function set
2450 * This enables the user to execute stdWrap functions in another than the predefined order
2451 * It modifies the content, not the property
2452 * while the new feature of chained stdWrap functions modifies the property and not the content
2453 *
2454 * @param string $content Input value undergoing processing in this function.
2455 * @param array $conf stdWrap properties for stdWrap.
2456 * @return string The processed input value
2457 */
2458 public function stdWrap_stdWrap($content = '', $conf = [])
2459 {
2460 return $this->stdWrap($content, $conf['stdWrap.']);
2461 }
2462
2463 /**
2464 * stdWrap process hook
2465 * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
2466 * this hook executes functions directly after the recursive stdWrap function call but still before the content gets modified
2467 *
2468 * @param string $content Input value undergoing processing in these functions.
2469 * @param array $conf All stdWrap properties, not just the ones for a particular function.
2470 * @return string The processed input value
2471 */
2472 public function stdWrap_stdWrapProcess($content = '', $conf = [])
2473 {
2474 foreach ($this->stdWrapHookObjects as $hookObject) {
2475 /** @var ContentObjectStdWrapHookInterface $hookObject */
2476 $content = $hookObject->stdWrapProcess($content, $conf, $this);
2477 }
2478 return $content;
2479 }
2480
2481 /**
2482 * required
2483 * Will immediately stop rendering and return an empty value
2484 * when there is no content at this point
2485 *
2486 * @param string $content Input value undergoing processing in this function.
2487 * @return string The processed input value
2488 */
2489 public function stdWrap_required($content = '')
2490 {
2491 if ((string)$content === '') {
2492 $content = '';
2493 $this->stopRendering[$this->stdWrapRecursionLevel] = true;
2494 }
2495 return $content;
2496 }
2497
2498 /**
2499 * if
2500 * Will immediately stop rendering and return an empty value
2501 * when the result of the checks returns FALSE
2502 *
2503 * @param string $content Input value undergoing processing in this function.
2504 * @param array $conf stdWrap properties for if.
2505 * @return string The processed input value
2506 */
2507 public function stdWrap_if($content = '', $conf = [])
2508 {
2509 if (empty($conf['if.']) || $this->checkIf($conf['if.'])) {
2510 return $content;
2511 }
2512 $this->stopRendering[$this->stdWrapRecursionLevel] = true;
2513 return '';
2514 }
2515
2516 /**
2517 * fieldRequired
2518 * Will immediately stop rendering and return an empty value
2519 * when there is no content in the field given by fieldRequired
2520 *
2521 * @param string $content Input value undergoing processing in this function.
2522 * @param array $conf stdWrap properties for fieldRequired.
2523 * @return string The processed input value
2524 */
2525 public function stdWrap_fieldRequired($content = '', $conf = [])
2526 {
2527 if (!trim($this->data[$conf['fieldRequired']])) {
2528 $content = '';
2529 $this->stopRendering[$this->stdWrapRecursionLevel] = true;
2530 }
2531 return $content;
2532 }
2533
2534 /**
2535 * csConv
2536 * Will convert the current chracter set of the content to the one given in csConv
2537 *
2538 * @param string $content Input value undergoing processing in this function.
2539 * @param array $conf stdWrap properties for csConv.
2540 * @return string The processed input value
2541 */
2542 public function stdWrap_csConv($content = '', $conf = [])
2543 {
2544 if (!empty($conf['csConv'])) {
2545 /** @var CharsetConverter $charsetConverter */
2546 $charsetConverter = GeneralUtility::makeInstance(CharsetConverter::class);
2547 $output = $charsetConverter->conv($content, $charsetConverter->parse_charset($conf['csConv']), 'utf-8');
2548 return $output ?: $content;
2549 } else {
2550 return $content;
2551 }
2552 }
2553
2554 /**
2555 * parseFunc
2556 * Will parse the content based on functions given as stdWrap properties
2557 * Heavily used together with RTE based content
2558 *
2559 * @param string $content Input value undergoing processing in this function.
2560 * @param array $conf stdWrap properties for parseFunc.
2561 * @return string The processed input value
2562 */
2563 public function stdWrap_parseFunc($content = '', $conf = [])
2564 {
2565 return $this->parseFunc($content, $conf['parseFunc.'], $conf['parseFunc']);
2566 }
2567
2568 /**
2569 * HTMLparser
2570 * Will parse HTML content based on functions given as stdWrap properties
2571 * Heavily used together with RTE based content
2572 *
2573 * @param string $content Input value undergoing processing in this function.
2574 * @param array $conf stdWrap properties for HTMLparser.
2575 * @return string The processed input value
2576 */
2577 public function stdWrap_HTMLparser($content = '', $conf = [])
2578 {
2579 if (is_array($conf['HTMLparser.'])) {
2580 $content = $this->HTMLparser_TSbridge($content, $conf['HTMLparser.']);
2581 }
2582 return $content;
2583 }
2584
2585 /**
2586 * split
2587 * Will split the content by a given token and treat the results separately
2588 * Automatically fills 'current' with a single result
2589 *
2590 * @param string $content Input value undergoing processing in this function.
2591 * @param array $conf stdWrap properties for split.
2592 * @return string The processed input value
2593 */
2594 public function stdWrap_split($content = '', $conf = [])
2595 {
2596 return $this->splitObj($content, $conf['split.']);
2597 }
2598
2599 /**
2600 * replacement
2601 * Will execute replacements on the content (optionally with preg-regex)
2602 *
2603 * @param string $content Input value undergoing processing in this function.
2604 * @param array $conf stdWrap properties for replacement.
2605 * @return string The processed input value
2606 */
2607 public function stdWrap_replacement($content = '', $conf = [])
2608 {
2609 return $this->replacement($content, $conf['replacement.']);
2610 }
2611
2612 /**
2613 * prioriCalc
2614 * Will use the content as a mathematical term and calculate the result
2615 * Can be set to 1 to just get a calculated value or 'intval' to get the integer of the result
2616 *
2617 * @param string $content Input value undergoing processing in this function.
2618 * @param array $conf stdWrap properties for prioriCalc.
2619 * @return string The processed input value
2620 */
2621 public function stdWrap_prioriCalc($content = '', $conf = [])
2622 {
2623 $content = MathUtility::calculateWithParentheses($content);
2624 if ($conf['prioriCalc'] === 'intval') {
2625 $content = (int)$content;
2626 }
2627 return $content;
2628 }
2629
2630 /**
2631 * char
2632 * Returns a one-character string containing the character specified by ascii code.
2633 *
2634 * Reliable results only for character codes in the integer range 0 - 127.
2635 *
2636 * @see http://php.net/manual/en/function.chr.php
2637 * @param string $content Input value undergoing processing in this function.
2638 * @param array $conf stdWrap properties for char.
2639 * @return string The processed input value
2640 */
2641 public function stdWrap_char($content = '', $conf = [])
2642 {
2643 return chr((int)$conf['char']);
2644 }
2645
2646 /**
2647 * intval
2648 * Will return an integer value of the current content
2649 *
2650 * @param string $content Input value undergoing processing in this function.
2651 * @return string The processed input value
2652 */
2653 public function stdWrap_intval($content = '')
2654 {
2655 return (int)$content;
2656 }
2657
2658 /**
2659 * Will return a hashed value of the current content
2660 *
2661 * @param string $content Input value undergoing processing in this function.
2662 * @param array $conf stdWrap properties for hash.
2663 * @return string The processed input value
2664 * @link http://php.net/manual/de/function.hash-algos.php for a list of supported hash algorithms
2665 */
2666 public function stdWrap_hash($content = '', array $conf = [])
2667 {
2668 $algorithm = isset($conf['hash.']) ? $this->stdWrap($conf['hash'], $conf['hash.']) : $conf['hash'];
2669 if (function_exists('hash') && in_array($algorithm, hash_algos())) {
2670 return hash($algorithm, $content);
2671 }
2672 // Non-existing hashing algorithm
2673 return '';
2674 }
2675
2676 /**
2677 * stdWrap_round will return a rounded number with ceil(), floor() or round(), defaults to round()
2678 * Only the english number format is supported . (dot) as decimal point
2679 *
2680 * @param string $content Input value undergoing processing in this function.
2681 * @param array $conf stdWrap properties for round.
2682 * @return string The processed input value
2683 */
2684 public function stdWrap_round($content = '', $conf = [])
2685 {
2686 return $this->round($content, $conf['round.']);
2687 }
2688
2689 /**
2690 * numberFormat
2691 * Will return a formatted number based on configuration given as stdWrap properties
2692 *
2693 * @param string $content Input value undergoing processing in this function.
2694 * @param array $conf stdWrap properties for numberFormat.
2695 * @return string The processed input value
2696 */
2697 public function stdWrap_numberFormat($content = '', $conf = [])
2698 {
2699 return $this->numberFormat($content, $conf['numberFormat.']);
2700 }
2701
2702 /**
2703 * expandList
2704 * Will return a formatted number based on configuration given as stdWrap properties
2705 *
2706 * @param string $content Input value undergoing processing in this function.
2707 * @return string The processed input value
2708 */
2709 public function stdWrap_expandList($content = '')
2710 {
2711 return GeneralUtility::expandList($content);
2712 }
2713
2714 /**
2715 * date
2716 * Will return a formatted date based on configuration given according to PHP date/gmdate properties
2717 * Will return gmdate when the property GMT returns TRUE
2718 *
2719 * @param string $content Input value undergoing processing in this function.
2720 * @param array $conf stdWrap properties for date.
2721 * @return string The processed input value
2722 */
2723 public function stdWrap_date($content = '', $conf = [])
2724 {
2725 // Check for zero length string to mimic default case of date/gmdate.
2726 $content = (string)$content === '' ? $GLOBALS['EXEC_TIME'] : (int)$content;
2727 $content = $conf['date.']['GMT'] ? gmdate($conf['date'], $content) : date($conf['date'], $content);
2728 return $content;
2729 }
2730
2731 /**
2732 * strftime
2733 * Will return a formatted date based on configuration given according to PHP strftime/gmstrftime properties
2734 * Will return gmstrftime when the property GMT returns TRUE
2735 *
2736 * @param string $content Input value undergoing processing in this function.
2737 * @param array $conf stdWrap properties for strftime.
2738 * @return string The processed input value
2739 */
2740 public function stdWrap_strftime($content = '', $conf = [])
2741 {
2742 // Check for zero length string to mimic default case of strtime/gmstrftime
2743 $content = (string)$content === '' ? $GLOBALS['EXEC_TIME'] : (int)$content;
2744 $content = $conf['strftime.']['GMT'] ? gmstrftime($conf['strftime'], $content) : strftime($conf['strftime'], $content);
2745 if (!empty($conf['strftime.']['charset'])) {
2746 /** @var CharsetConverter $charsetConverter */
2747 $charsetConverter = GeneralUtility::makeInstance(CharsetConverter::class);
2748 $output = $charsetConverter->conv($content, $charsetConverter->parse_charset($conf['strftime.']['charset']), 'utf-8');
2749 return $output ?: $content;
2750 }
2751 return $content;
2752 }
2753
2754 /**
2755 * strtotime
2756 * Will return a timestamp based on configuration given according to PHP strtotime
2757 *
2758 * @param string $content Input value undergoing processing in this function.
2759 * @param array $conf stdWrap properties for strtotime.
2760 * @return string The processed input value
2761 */
2762 public function stdWrap_strtotime($content = '', $conf = [])
2763 {
2764 if ($conf['strtotime'] !== '1') {
2765 $content .= ' ' . $conf['strtotime'];
2766 }
2767 return strtotime($content, $GLOBALS['EXEC_TIME']);
2768 }
2769
2770 /**
2771 * age
2772 * Will return the age of a given timestamp based on configuration given by stdWrap properties
2773 *
2774 * @param string $content Input value undergoing processing in this function.
2775 * @param array $conf stdWrap properties for age.
2776 * @return string The processed input value
2777 */
2778 public function stdWrap_age($content = '', $conf = [])
2779 {
2780 return $this->calcAge((int)$GLOBALS['EXEC_TIME'] - (int)$content, $conf['age']);
2781 }
2782
2783 /**
2784 * case
2785 * Will transform the content to be upper or lower case only
2786 * Leaves HTML tags untouched
2787 *
2788 * @param string $content Input value undergoing processing in this function.
2789 * @param array $conf stdWrap properties for case.
2790 * @return string The processed input value
2791 */
2792 public function stdWrap_case($content = '', $conf = [])
2793 {
2794 return $this->HTMLcaseshift($content, $conf['case']);
2795 }
2796
2797 /**
2798 * bytes
2799 * Will return the size of a given number in Bytes *
2800 *
2801 * @param string $content Input value undergoing processing in this function.
2802 * @param array $conf stdWrap properties for bytes.
2803 * @return string The processed input value
2804 */
2805 public function stdWrap_bytes($content = '', $conf = [])
2806 {
2807 return GeneralUtility::formatSize($content, $conf['bytes.']['labels'], $conf['bytes.']['base']);
2808 }
2809
2810 /**
2811 * substring
2812 * Will return a substring based on position information given by stdWrap properties
2813 *
2814 * @param string $content Input value undergoing processing in this function.
2815 * @param array $conf stdWrap properties for substring.
2816 * @return string The processed input value
2817 */
2818 public function stdWrap_substring($content = '', $conf = [])
2819 {
2820 return $this->substring($content, $conf['substring']);
2821 }
2822
2823 /**
2824 * removeBadHTML
2825 * Removes HTML tags based on stdWrap properties
2826 *
2827 * @param string $content Input value undergoing processing in this function.
2828 * @return string The processed input value
2829 * @deprecated since TYPO3 v8, will be removed in TYPO3 v9
2830 */
2831 public function stdWrap_removeBadHTML($content = '')
2832 {
2833 return $this->removeBadHTML($content);
2834 }
2835
2836 /**
2837 * cropHTML
2838 * Crops content to a given size while leaving HTML tags untouched
2839 *
2840 * @param string $content Input value undergoing processing in this function.
2841 * @param array $conf stdWrap properties for cropHTML.
2842 * @return string The processed input value
2843 */
2844 public function stdWrap_cropHTML($content = '', $conf = [])
2845 {
2846 return $this->cropHTML($content, $conf['cropHTML']);
2847 }
2848
2849 /**
2850 * stripHtml
2851 * Copmletely removes HTML tags from content
2852 *
2853 * @param string $content Input value undergoing processing in this function.
2854 * @return string The processed input value
2855 */
2856 public function stdWrap_stripHtml($content = '')
2857 {
2858 return strip_tags($content);
2859 }
2860
2861 /**
2862 * crop
2863 * Crops content to a given size without caring about HTML tags
2864 *
2865 * @param string $content Input value undergoing processing in this function.
2866 * @param array $conf stdWrap properties for crop.
2867 * @return string The processed input value
2868 */
2869 public function stdWrap_crop($content = '', $conf = [])
2870 {
2871 return $this->crop($content, $conf['crop']);
2872 }
2873
2874 /**
2875 * rawUrlEncode
2876 * Encodes content to be used within URLs
2877 *
2878 * @param string $content Input value undergoing processing in this function.
2879 * @return string The processed input value
2880 */
2881 public function stdWrap_rawUrlEncode($content = '')
2882 {
2883 return rawurlencode($content);
2884 }
2885
2886 /**
2887 * htmlSpecialChars
2888 * Transforms HTML tags to readable text by replacing special characters with their HTML entity
2889 * When preserveEntities returns TRUE, existing entities will be left untouched
2890 *
2891 * @param string $content Input value undergoing processing in this function.
2892 * @param array $conf stdWrap properties for htmlSpecalChars.
2893 * @return string The processed input value
2894 */
2895 public function stdWrap_htmlSpecialChars($content = '', $conf = [])
2896 {
2897 if (!empty($conf['htmlSpecialChars.']['preserveEntities'])) {
2898 $content = htmlspecialchars($content, ENT_COMPAT, 'UTF-8', false);
2899 } else {
2900 $content = htmlspecialchars($content);
2901 }
2902 return $content;
2903 }
2904
2905 /**
2906 * encodeForJavaScriptValue
2907 * Escapes content to be used inside JavaScript strings. No quotes are added around the value
2908 * as this can easily be done in TypoScript
2909 *
2910 * @param string $content Input value undergoing processing in this function
2911 * @return string The processed input value
2912 */
2913 public function stdWrap_encodeForJavaScriptValue($content = '')
2914 {
2915 return GeneralUtility::quoteJSvalue($content);
2916 }
2917
2918 /**
2919 * doubleBrTag
2920 * Searches for double line breaks and replaces them with the given value
2921 *
2922 * @param string $content Input value undergoing processing in this function.
2923 * @param array $conf stdWrap properties for doubleBrTag.
2924 * @return string The processed input value
2925 */
2926 public function stdWrap_doubleBrTag($content = '', $conf = [])
2927 {
2928 return preg_replace('/\R{1,2}[\t\x20]*\R{1,2}/', $conf['doubleBrTag'], $content);
2929 }
2930
2931 /**
2932 * br
2933 * Searches for single line breaks and replaces them with a <br />/<br> tag
2934 * according to the doctype
2935 *
2936 * @param string $content Input value undergoing processing in this function.
2937 * @param array $conf stdWrap properties for br.
2938 * @return string The processed input value
2939 */
2940 public function stdWrap_br($content = '')
2941 {
2942 return nl2br($content, !empty($this->getTypoScriptFrontendController()->xhtmlDoctype));
2943 }
2944
2945 /**
2946 * brTag
2947 * Searches for single line feeds and replaces them with the given value
2948 *
2949 * @param string $content Input value undergoing processing in this function.
2950 * @param array $conf stdWrap properties for brTag.
2951 * @return string The processed input value
2952 */
2953 public function stdWrap_brTag($content = '', $conf = [])
2954 {
2955 return str_replace(LF, $conf['brTag'], $content);
2956 }
2957
2958 /**
2959 * encapsLines
2960 * Modifies text blocks by searching for lines which are not surrounded by HTML tags yet
2961 * and wrapping them with values given by stdWrap properties
2962 *
2963 * @param string $content Input value undergoing processing in this function.
2964 * @param array $conf stdWrap properties for erncapsLines.
2965 * @return string The processed input value
2966 */
2967 public function stdWrap_encapsLines($content = '', $conf = [])
2968 {
2969 return $this->encaps_lineSplit($content, $conf['encapsLines.']);