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