TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
ContentObjectRenderer.php
Go to the documentation of this file.
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;
63 
74 {
78  public $align = [
79  'center',
80  'right',
81  'left'
82  ];
83 
89  public $stdWrapOrder = [
90  'stdWrapPreProcess' => 'hook',
91  // this is a placeholder for the first Hook
92  'cacheRead' => 'hook',
93  // this is a placeholder for checking if the content is available in cache
94  'setContentToCurrent' => 'boolean',
95  'setContentToCurrent.' => 'array',
96  'addPageCacheTags' => 'string',
97  'addPageCacheTags.' => 'array',
98  'setCurrent' => 'string',
99  'setCurrent.' => 'array',
100  'lang.' => 'array',
101  'data' => 'getText',
102  'data.' => 'array',
103  'field' => 'fieldName',
104  'field.' => 'array',
105  'current' => 'boolean',
106  'current.' => 'array',
107  'cObject' => 'cObject',
108  'cObject.' => 'array',
109  'numRows.' => 'array',
110  'filelist' => 'dir',
111  'filelist.' => 'array',
112  'preUserFunc' => 'functionName',
113  'stdWrapOverride' => 'hook',
114  // this is a placeholder for the second Hook
115  'override' => 'string',
116  'override.' => 'array',
117  'preIfEmptyListNum' => 'listNum',
118  'preIfEmptyListNum.' => 'array',
119  'ifNull' => 'string',
120  'ifNull.' => 'array',
121  'ifEmpty' => 'string',
122  'ifEmpty.' => 'array',
123  'ifBlank' => 'string',
124  'ifBlank.' => 'array',
125  'listNum' => 'listNum',
126  'listNum.' => 'array',
127  'trim' => 'boolean',
128  'trim.' => 'array',
129  'strPad.' => 'array',
130  'stdWrap' => 'stdWrap',
131  'stdWrap.' => 'array',
132  'stdWrapProcess' => 'hook',
133  // this is a placeholder for the third Hook
134  'required' => 'boolean',
135  'required.' => 'array',
136  'if.' => 'array',
137  'fieldRequired' => 'fieldName',
138  'fieldRequired.' => 'array',
139  'csConv' => 'string',
140  'csConv.' => 'array',
141  'parseFunc' => 'objectpath',
142  'parseFunc.' => 'array',
143  'HTMLparser' => 'boolean',
144  'HTMLparser.' => 'array',
145  'split.' => 'array',
146  'replacement.' => 'array',
147  'prioriCalc' => 'boolean',
148  'prioriCalc.' => 'array',
149  'char' => 'integer',
150  'char.' => 'array',
151  'intval' => 'boolean',
152  'intval.' => 'array',
153  'hash' => 'string',
154  'hash.' => 'array',
155  'round' => 'boolean',
156  'round.' => 'array',
157  'numberFormat.' => 'array',
158  'expandList' => 'boolean',
159  'expandList.' => 'array',
160  'date' => 'dateconf',
161  'date.' => 'array',
162  'strtotime' => 'strtotimeconf',
163  'strtotime.' => 'array',
164  'strftime' => 'strftimeconf',
165  'strftime.' => 'array',
166  'age' => 'boolean',
167  'age.' => 'array',
168  'case' => 'case',
169  'case.' => 'array',
170  'bytes' => 'boolean',
171  'bytes.' => 'array',
172  'substring' => 'parameters',
173  'substring.' => 'array',
174  'removeBadHTML' => 'boolean',
175  'removeBadHTML.' => 'array',
176  'cropHTML' => 'crop',
177  'cropHTML.' => 'array',
178  'stripHtml' => 'boolean',
179  'stripHtml.' => 'array',
180  'crop' => 'crop',
181  'crop.' => 'array',
182  'rawUrlEncode' => 'boolean',
183  'rawUrlEncode.' => 'array',
184  'htmlSpecialChars' => 'boolean',
185  'htmlSpecialChars.' => 'array',
186  'encodeForJavaScriptValue' => 'boolean',
187  'encodeForJavaScriptValue.' => 'array',
188  'doubleBrTag' => 'string',
189  'doubleBrTag.' => 'array',
190  'br' => 'boolean',
191  'br.' => 'array',
192  'brTag' => 'string',
193  'brTag.' => 'array',
194  'encapsLines.' => 'array',
195  'keywords' => 'boolean',
196  'keywords.' => 'array',
197  'innerWrap' => 'wrap',
198  'innerWrap.' => 'array',
199  'innerWrap2' => 'wrap',
200  'innerWrap2.' => 'array',
201  'fontTag' => 'wrap',
202  'fontTag.' => 'array',
203  'addParams.' => 'array',
204  'filelink.' => 'array',
205  'preCObject' => 'cObject',
206  'preCObject.' => 'array',
207  'postCObject' => 'cObject',
208  'postCObject.' => 'array',
209  'wrapAlign' => 'align',
210  'wrapAlign.' => 'array',
211  'typolink.' => 'array',
212  'TCAselectItem.' => 'array',
213  'space' => 'space',
214  'space.' => 'array',
215  'spaceBefore' => 'int',
216  'spaceBefore.' => 'array',
217  'spaceAfter' => 'int',
218  'spaceAfter.' => 'array',
219  'wrap' => 'wrap',
220  'wrap.' => 'array',
221  'noTrimWrap' => 'wrap',
222  'noTrimWrap.' => 'array',
223  'wrap2' => 'wrap',
224  'wrap2.' => 'array',
225  'dataWrap' => 'dataWrap',
226  'dataWrap.' => 'array',
227  'prepend' => 'cObject',
228  'prepend.' => 'array',
229  'append' => 'cObject',
230  'append.' => 'array',
231  'wrap3' => 'wrap',
232  'wrap3.' => 'array',
233  'orderedStdWrap' => 'stdWrap',
234  'orderedStdWrap.' => 'array',
235  'outerWrap' => 'wrap',
236  'outerWrap.' => 'array',
237  'insertData' => 'boolean',
238  'insertData.' => 'array',
239  'postUserFunc' => 'functionName',
240  'postUserFuncInt' => 'functionName',
241  'prefixComment' => 'string',
242  'prefixComment.' => 'array',
243  'editIcons' => 'string',
244  'editIcons.' => 'array',
245  'editPanel' => 'boolean',
246  'editPanel.' => 'array',
247  'cacheStore' => 'hook',
248  // this is a placeholder for storing the content in cache
249  'stdWrapPostProcess' => 'hook',
250  // this is a placeholder for the last Hook
251  'debug' => 'boolean',
252  'debug.' => 'array',
253  'debugFunc' => 'boolean',
254  'debugFunc.' => 'array',
255  'debugData' => 'boolean',
256  'debugData.' => 'array'
257  ];
258 
264  protected $contentObjectClassMap = [];
265 
273  10 => [
274  'params' => '',
275  'ext' => 'gif'
276  ],
277  11 => [
278  'params' => '-colors 128',
279  'ext' => 'gif'
280  ],
281  12 => [
282  'params' => '-colors 64',
283  'ext' => 'gif'
284  ],
285  13 => [
286  'params' => '-colors 32',
287  'ext' => 'gif'
288  ],
289  14 => [
290  'params' => '-colors 16',
291  'ext' => 'gif'
292  ],
293  15 => [
294  'params' => '-colors 8',
295  'ext' => 'gif'
296  ],
297  20 => [
298  'params' => '-quality 100',
299  'ext' => 'jpg'
300  ],
301  21 => [
302  'params' => '-quality 90',
303  'ext' => 'jpg'
304  ],
305  22 => [
306  'params' => '-quality 80',
307  'ext' => 'jpg'
308  ],
309  23 => [
310  'params' => '-quality 70',
311  'ext' => 'jpg'
312  ],
313  24 => [
314  'params' => '-quality 60',
315  'ext' => 'jpg'
316  ],
317  25 => [
318  'params' => '-quality 50',
319  'ext' => 'jpg'
320  ],
321  26 => [
322  'params' => '-quality 40',
323  'ext' => 'jpg'
324  ],
325  27 => [
326  'params' => '-quality 30',
327  'ext' => 'jpg'
328  ],
329  28 => [
330  'params' => '-quality 20',
331  'ext' => 'jpg'
332  ],
333  30 => [
334  'params' => '-colors 256',
335  'ext' => 'png'
336  ],
337  31 => [
338  'params' => '-colors 128',
339  'ext' => 'png'
340  ],
341  32 => [
342  'params' => '-colors 64',
343  'ext' => 'png'
344  ],
345  33 => [
346  'params' => '-colors 32',
347  'ext' => 'png'
348  ],
349  34 => [
350  'params' => '-colors 16',
351  'ext' => 'png'
352  ],
353  35 => [
354  'params' => '-colors 8',
355  'ext' => 'png'
356  ],
357  39 => [
358  'params' => '',
359  'ext' => 'png'
360  ]
361  ];
362 
369  public $image_effects = [
370  1 => '-rotate 90',
371  2 => '-rotate 270',
372  3 => '-rotate 180',
373  10 => '-colorspace GRAY',
374  11 => '-sharpen 70',
375  20 => '-normalize',
376  23 => '-contrast',
377  25 => '-gamma 1.3',
378  26 => '-gamma 0.8'
379  ];
380 
390  public $data = [];
391 
395  protected $table = '';
396 
402  public $oldData = [];
403 
409  public $alternativeData = '';
410 
416  public $parameters = [];
417 
421  public $currentValKey = 'currentValue_kidjls9dksoje';
422 
429  public $currentRecord = '';
430 
437 
444 
451 
457  public $parentRecord = [];
458 
464  public $checkPid_cache = [];
465 
469  public $checkPid_badDoktypeList = '255';
470 
476  public $lastTypoLinkUrl = '';
477 
483  public $lastTypoLinkTarget = '';
484 
488  public $lastTypoLinkLD = [];
489 
495  public $substMarkerCache = [];
496 
502  public $recordRegister = [];
503 
509  protected $cObjHookObjectsRegistry = [];
510 
514  public $cObjHookObjectsArr = [];
515 
521  protected $stdWrapHookObjects = [];
522 
529 
533  protected $currentFile = null;
534 
539 
545  protected $userObjectType = false;
546 
550  protected $stopRendering = [];
551 
555  protected $stdWrapRecursionLevel = 0;
556 
561 
565  protected $templateService;
566 
578  const OBJECTTYPE_USER = 2;
579 
584  {
585  $this->typoScriptFrontendController = $typoScriptFrontendController;
586  $this->contentObjectClassMap = $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'];
587  $this->templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
588  }
589 
597  public function __sleep()
598  {
599  $vars = get_object_vars($this);
600  unset($vars['typoScriptFrontendController']);
601  if ($this->currentFile instanceof FileReference) {
602  $this->currentFile = 'FileReference:' . $this->currentFile->getUid();
603  } elseif ($this->currentFile instanceof File) {
604  $this->currentFile = 'File:' . $this->currentFile->getIdentifier();
605  } else {
606  unset($vars['currentFile']);
607  }
608  return array_keys($vars);
609  }
610 
616  public function __wakeup()
617  {
618  if (isset($GLOBALS['TSFE'])) {
619  $this->typoScriptFrontendController = $GLOBALS['TSFE'];
620  }
621  if ($this->currentFile !== null && is_string($this->currentFile)) {
622  list($objectType, $identifier) = explode(':', $this->currentFile, 2);
623  try {
624  if ($objectType === 'File') {
625  $this->currentFile = ResourceFactory::getInstance()->retrieveFileOrFolderObject($identifier);
626  } elseif ($objectType === 'FileReference') {
627  $this->currentFile = ResourceFactory::getInstance()->getFileReferenceObject($identifier);
628  }
629  } catch (ResourceDoesNotExistException $e) {
630  $this->currentFile = null;
631  }
632  }
633  }
634 
645  {
646  $this->contentObjectClassMap = $contentObjectClassMap;
647  }
648 
659  public function registerContentObjectClass($className, $contentObjectName)
660  {
661  $this->contentObjectClassMap[$contentObjectName] = $className;
662  }
663 
673  public function start($data, $table = '')
674  {
675  $this->data = $data;
676  $this->table = $table;
677  $this->currentRecord = $table !== '' ? $table . ':' . $this->data['uid'] : '';
678  $this->parameters = [];
679  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['cObjTypeAndClass'])) {
680  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['cObjTypeAndClass'] as $classArr) {
681  $this->cObjHookObjectsRegistry[$classArr[0]] = $classArr[1];
682  }
683  }
684  $this->stdWrapHookObjects = [];
685  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap'])) {
686  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap'] as $classData) {
687  $hookObject = GeneralUtility::getUserObj($classData);
688  if (!$hookObject instanceof ContentObjectStdWrapHookInterface) {
689  throw new \UnexpectedValueException($classData . ' must implement interface ' . ContentObjectStdWrapHookInterface::class, 1195043965);
690  }
691  $this->stdWrapHookObjects[] = $hookObject;
692  }
693  }
694  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['postInit'])) {
695  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['postInit'] as $classData) {
696  $postInitializationProcessor = GeneralUtility::getUserObj($classData);
697  if (!$postInitializationProcessor instanceof ContentObjectPostInitHookInterface) {
698  throw new \UnexpectedValueException($classData . ' must implement interface ' . ContentObjectPostInitHookInterface::class, 1274563549);
699  }
700  $postInitializationProcessor->postProcessContentObjectInitialization($this);
701  }
702  }
703  }
704 
710  public function getCurrentTable()
711  {
712  return $this->table;
713  }
714 
721  protected function getGetImgResourceHookObjects()
722  {
723  if (!isset($this->getImgResourceHookObjects)) {
724  $this->getImgResourceHookObjects = [];
725  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImgResource'])) {
726  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImgResource'] as $classData) {
727  $hookObject = GeneralUtility::getUserObj($classData);
728  if (!$hookObject instanceof ContentObjectGetImageResourceHookInterface) {
729  throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetImageResourceHookInterface::class, 1218636383);
730  }
731  $this->getImgResourceHookObjects[] = $hookObject;
732  }
733  }
734  }
736  }
737 
747  public function setParent($data, $currentRecord)
748  {
749  $this->parentRecord = [
750  'data' => $data,
751  'currentRecord' => $currentRecord
752  ];
753  }
754 
755  /***********************************************
756  *
757  * CONTENT_OBJ:
758  *
759  ***********************************************/
768  public function getCurrentVal()
769  {
770  return $this->data[$this->currentValKey];
771  }
772 
780  public function setCurrentVal($value)
781  {
782  $this->data[$this->currentValKey] = $value;
783  }
784 
794  public function cObjGet($setup, $addKey = '')
795  {
796  if (!is_array($setup)) {
797  return '';
798  }
799  $sKeyArray = ArrayUtility::filterAndSortByNumericKeys($setup);
800  $content = '';
801  foreach ($sKeyArray as $theKey) {
802  $theValue = $setup[$theKey];
803  if ((int)$theKey && strpos($theKey, '.') === false) {
804  $conf = $setup[$theKey . '.'];
805  $content .= $this->cObjGetSingle($theValue, $conf, $addKey . $theKey);
806  }
807  }
808  return $content;
809  }
810 
820  public function cObjGetSingle($name, $conf, $TSkey = '__')
821  {
822  $content = '';
823  // Checking that the function is not called eternally. This is done by interrupting at a depth of 100
824  $this->getTypoScriptFrontendController()->cObjectDepthCounter--;
825  if ($this->getTypoScriptFrontendController()->cObjectDepthCounter > 0) {
826  $timeTracker = $this->getTimeTracker();
827  $name = trim($name);
828  if ($timeTracker->LR) {
829  $timeTracker->push($TSkey, $name);
830  }
831  // Checking if the COBJ is a reference to another object. (eg. name of 'blabla.blabla = < styles.something')
832  if ($name[0] === '<') {
833  $key = trim(substr($name, 1));
834  $cF = GeneralUtility::makeInstance(TypoScriptParser::class);
835  // $name and $conf is loaded with the referenced values.
836  $confOverride = is_array($conf) ? $conf : [];
837  list($name, $conf) = $cF->getVal($key, $this->getTypoScriptFrontendController()->tmpl->setup);
838  $conf = array_replace_recursive(is_array($conf) ? $conf : [], $confOverride);
839  // Getting the cObject
840  $timeTracker->incStackPointer();
841  $content .= $this->cObjGetSingle($name, $conf, $key);
842  $timeTracker->decStackPointer();
843  } else {
844  $hooked = false;
845  // Application defined cObjects
846  if (!empty($this->cObjHookObjectsRegistry[$name])) {
847  if (empty($this->cObjHookObjectsArr[$name])) {
848  $this->cObjHookObjectsArr[$name] = GeneralUtility::getUserObj($this->cObjHookObjectsRegistry[$name]);
849  }
850  $hookObj = $this->cObjHookObjectsArr[$name];
851  if (method_exists($hookObj, 'cObjGetSingleExt')) {
852  $content .= $hookObj->cObjGetSingleExt($name, $conf, $TSkey, $this);
853  $hooked = true;
854  }
855  }
856  if (!$hooked) {
857  $contentObject = $this->getContentObject($name);
858  if ($contentObject) {
859  $content .= $this->render($contentObject, $conf);
860  } else {
861  // Call hook functions for extra processing
862  if ($name && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['cObjTypeAndClassDefault'])) {
863  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['cObjTypeAndClassDefault'] as $classData) {
864  $hookObject = GeneralUtility::getUserObj($classData);
865  if (!$hookObject instanceof ContentObjectGetSingleHookInterface) {
866  throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetSingleHookInterface::class, 1195043731);
867  }
869  $content .= $hookObject->getSingleContentObject($name, (array)$conf, $TSkey, $this);
870  }
871  } else {
872  // Log error in AdminPanel
873  $warning = sprintf('Content Object "%s" does not exist', $name);
874  $timeTracker->setTSlogMessage($warning, 2);
875  }
876  }
877  }
878  }
879  if ($timeTracker->LR) {
880  $timeTracker->pull($content);
881  }
882  }
883  // Increasing on exit...
884  $this->getTypoScriptFrontendController()->cObjectDepthCounter++;
885  return $content;
886  }
887 
897  public function getContentObject($name)
898  {
899  if (!isset($this->contentObjectClassMap[$name])) {
900  return null;
901  }
902  $fullyQualifiedClassName = $this->contentObjectClassMap[$name];
903  $contentObject = GeneralUtility::makeInstance($fullyQualifiedClassName, $this);
904  if (!($contentObject instanceof AbstractContentObject)) {
905  throw new ContentRenderingException(sprintf('Registered content object class name "%s" must be an instance of AbstractContentObject, but is not!', $fullyQualifiedClassName), 1422564295);
906  }
907  return $contentObject;
908  }
909 
910  /********************************************
911  *
912  * Functions rendering content objects (cObjects)
913  *
914  ********************************************/
915 
927  public function render(AbstractContentObject $contentObject, $configuration = [])
928  {
929  $content = '';
930 
931  // Evaluate possible cache and return
932  $cacheConfiguration = isset($configuration['cache.']) ? $configuration['cache.'] : null;
933  if ($cacheConfiguration !== null) {
934  unset($configuration['cache.']);
935  $cache = $this->getFromCache($cacheConfiguration);
936  if ($cache !== false) {
937  return $cache;
938  }
939  }
940 
941  // Render content
942  try {
943  $content .= $contentObject->render($configuration);
944  } catch (ContentRenderingException $exception) {
945  // Content rendering Exceptions indicate a critical problem which should not be
946  // caught e.g. when something went wrong with Exception handling itself
947  throw $exception;
948  } catch (\Exception $exception) {
949  $exceptionHandler = $this->createExceptionHandler($configuration);
950  if ($exceptionHandler === null) {
951  throw $exception;
952  } else {
953  $content = $exceptionHandler->handle($exception, $contentObject, $configuration);
954  }
955  }
956 
957  // Store cache
958  if ($cacheConfiguration !== null) {
959  $key = $this->calculateCacheKey($cacheConfiguration);
960  if (!empty($key)) {
962  $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash');
963  $tags = $this->calculateCacheTags($cacheConfiguration);
964  $lifetime = $this->calculateCacheLifetime($cacheConfiguration);
965  $cacheFrontend->set($key, $content, $tags, $lifetime);
966  }
967  }
968 
969  return $content;
970  }
971 
980  protected function createExceptionHandler($configuration = [])
981  {
982  $exceptionHandler = null;
983  $exceptionHandlerClassName = $this->determineExceptionHandlerClassName($configuration);
984  if (!empty($exceptionHandlerClassName)) {
985  $exceptionHandler = GeneralUtility::makeInstance($exceptionHandlerClassName, $this->mergeExceptionHandlerConfiguration($configuration));
986  if (!$exceptionHandler instanceof ExceptionHandlerInterface) {
987  throw new ContentRenderingException('An exception handler was configured but the class does not exist or does not implement the ExceptionHandlerInterface', 1403653369);
988  }
989  }
990 
991  return $exceptionHandler;
992  }
993 
1000  protected function determineExceptionHandlerClassName($configuration)
1001  {
1002  $exceptionHandlerClassName = null;
1003  $tsfe = $this->getTypoScriptFrontendController();
1004  if (!isset($tsfe->config['config']['contentObjectExceptionHandler'])) {
1005  if (GeneralUtility::getApplicationContext()->isProduction()) {
1006  $exceptionHandlerClassName = '1';
1007  }
1008  } else {
1009  $exceptionHandlerClassName = $tsfe->config['config']['contentObjectExceptionHandler'];
1010  }
1011 
1012  if (isset($configuration['exceptionHandler'])) {
1013  $exceptionHandlerClassName = $configuration['exceptionHandler'];
1014  }
1015 
1016  if ($exceptionHandlerClassName === '1') {
1017  $exceptionHandlerClassName = ProductionExceptionHandler::class;
1018  }
1019 
1020  return $exceptionHandlerClassName;
1021  }
1022 
1030  protected function mergeExceptionHandlerConfiguration($configuration)
1031  {
1032  $exceptionHandlerConfiguration = [];
1033  $tsfe = $this->getTypoScriptFrontendController();
1034  if (!empty($tsfe->config['config']['contentObjectExceptionHandler.'])) {
1035  $exceptionHandlerConfiguration = $tsfe->config['config']['contentObjectExceptionHandler.'];
1036  }
1037  if (!empty($configuration['exceptionHandler.'])) {
1038  $exceptionHandlerConfiguration = array_replace_recursive($exceptionHandlerConfiguration, $configuration['exceptionHandler.']);
1039  }
1040 
1041  return $exceptionHandlerConfiguration;
1042  }
1043 
1052  public function getUserObjectType()
1053  {
1054  return $this->userObjectType;
1055  }
1056 
1064  {
1065  $this->userObjectType = $userObjectType;
1066  }
1067 
1073  public function convertToUserIntObject()
1074  {
1075  if ($this->userObjectType !== self::OBJECTTYPE_USER) {
1076  $this->getTimeTracker()->setTSlogMessage(self::class . '::convertToUserIntObject() is called in the wrong context or for the wrong object type', 2);
1077  } else {
1078  $this->doConvertToUserIntObject = true;
1079  }
1080  }
1081 
1082  /************************************
1083  *
1084  * Various helper functions for content objects:
1085  *
1086  ************************************/
1095  public function readFlexformIntoConf($flexData, &$conf, $recursive = false)
1096  {
1097  if ($recursive === false && is_string($flexData)) {
1098  $flexData = GeneralUtility::xml2array($flexData, 'T3');
1099  }
1100  if (is_array($flexData) && isset($flexData['data']['sDEF']['lDEF'])) {
1101  $flexData = $flexData['data']['sDEF']['lDEF'];
1102  }
1103  if (!is_array($flexData)) {
1104  return;
1105  }
1106  foreach ($flexData as $key => $value) {
1107  if (!is_array($value)) {
1108  continue;
1109  }
1110  if (isset($value['el'])) {
1111  if (is_array($value['el']) && !empty($value['el'])) {
1112  foreach ($value['el'] as $ekey => $element) {
1113  if (isset($element['vDEF'])) {
1114  $conf[$ekey] = $element['vDEF'];
1115  } else {
1116  if (is_array($element)) {
1117  $this->readFlexformIntoConf($element, $conf[$key][key($element)][$ekey], true);
1118  } else {
1119  $this->readFlexformIntoConf($element, $conf[$key][$ekey], true);
1120  }
1121  }
1122  }
1123  } else {
1124  $this->readFlexformIntoConf($value['el'], $conf[$key], true);
1125  }
1126  }
1127  if (isset($value['vDEF'])) {
1128  $conf[$key] = $value['vDEF'];
1129  }
1130  }
1131  }
1132 
1141  public function getSlidePids($pidList, $pidConf)
1142  {
1143  $pidList = isset($pidConf) ? trim($this->stdWrap($pidList, $pidConf)) : trim($pidList);
1144  if ($pidList === '') {
1145  $pidList = 'this';
1146  }
1147  $tsfe = $this->getTypoScriptFrontendController();
1148  $listArr = null;
1149  if (trim($pidList)) {
1150  $listArr = GeneralUtility::intExplode(',', str_replace('this', $tsfe->contentPid, $pidList));
1151  $listArr = $this->checkPidArray($listArr);
1152  }
1153  $pidList = [];
1154  if (is_array($listArr) && !empty($listArr)) {
1155  foreach ($listArr as $uid) {
1156  $page = $tsfe->sys_page->getPage($uid);
1157  if (!$page['is_siteroot']) {
1158  $pidList[] = $page['pid'];
1159  }
1160  }
1161  }
1162  return implode(',', $pidList);
1163  }
1164 
1176  public function cImage($file, $conf)
1177  {
1178  $tsfe = $this->getTypoScriptFrontendController();
1179  $info = $this->getImgResource($file, $conf['file.']);
1180  $tsfe->lastImageInfo = $info;
1181  if (!is_array($info)) {
1182  return '';
1183  }
1184  if (is_file(PATH_site . $info['3'])) {
1185  $source = $tsfe->absRefPrefix . str_replace('%2F', '/', rawurlencode($info['3']));
1186  } else {
1187  $source = $info[3];
1188  }
1189 
1190  $layoutKey = $this->stdWrap($conf['layoutKey'], $conf['layoutKey.']);
1191  $imageTagTemplate = $this->getImageTagTemplate($layoutKey, $conf);
1192  $sourceCollection = $this->getImageSourceCollection($layoutKey, $conf, $file);
1193 
1194  // This array is used to collect the image-refs on the page...
1195  $tsfe->imagesOnPage[] = $source;
1196  $altParam = $this->getAltParam($conf);
1197  $params = $this->stdWrapValue('params', $conf);
1198  if ($params !== '' && $params[0] !== ' ') {
1199  $params = ' ' . $params;
1200  }
1201 
1202  $imageTagValues = [
1203  'width' => (int)$info[0],
1204  'height' => (int)$info[1],
1205  'src' => htmlspecialchars($source),
1206  'params' => $params,
1207  'altParams' => $altParam,
1208  'border' => $this->getBorderAttr(' border="' . (int)$conf['border'] . '"'),
1209  'sourceCollection' => $sourceCollection,
1210  'selfClosingTagSlash' => (!empty($tsfe->xhtmlDoctype) ? ' /' : ''),
1211  ];
1212 
1213  $theValue = $this->substituteMarkerArray($imageTagTemplate, $imageTagValues, '###|###', true, true);
1214 
1215  $linkWrap = isset($conf['linkWrap.']) ? $this->stdWrap($conf['linkWrap'], $conf['linkWrap.']) : $conf['linkWrap'];
1216  if ($linkWrap) {
1217  $theValue = $this->linkWrap($theValue, $linkWrap);
1218  } elseif ($conf['imageLinkWrap']) {
1219  $originalFile = !empty($info['originalFile']) ? $info['originalFile'] : $info['origFile'];
1220  $theValue = $this->imageLinkWrap($theValue, $originalFile, $conf['imageLinkWrap.']);
1221  }
1222  $wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
1223  if ((string)$wrap !== '') {
1224  $theValue = $this->wrap($theValue, $conf['wrap']);
1225  }
1226  return $theValue;
1227  }
1228 
1236  public function getBorderAttr($borderAttr)
1237  {
1238  $tsfe = $this->getTypoScriptFrontendController();
1239  $docType = $tsfe->xhtmlDoctype;
1240  if (
1241  $docType !== 'xhtml_strict' && $docType !== 'xhtml_11'
1242  && $tsfe->config['config']['doctype'] !== 'html5'
1243  && !$tsfe->config['config']['disableImgBorderAttr']
1244  ) {
1245  return $borderAttr;
1246  }
1247  return '';
1248  }
1249 
1258  public function getImageTagTemplate($layoutKey, $conf)
1259  {
1260  if ($layoutKey && isset($conf['layout.']) && isset($conf['layout.'][$layoutKey . '.'])) {
1261  $imageTagLayout = $this->stdWrap($conf['layout.'][$layoutKey . '.']['element'], $conf['layout.'][$layoutKey . '.']['element.']);
1262  } else {
1263  $imageTagLayout = '<img src="###SRC###" width="###WIDTH###" height="###HEIGHT###" ###PARAMS### ###ALTPARAMS### ###BORDER######SELFCLOSINGTAGSLASH###>';
1264  }
1265  return $imageTagLayout;
1266  }
1267 
1277  public function getImageSourceCollection($layoutKey, $conf, $file)
1278  {
1279  $sourceCollection = '';
1280  if ($layoutKey && $conf['sourceCollection.'] && ($conf['layout.'][$layoutKey . '.']['source'] || $conf['layout.'][$layoutKey . '.']['source.'])) {
1281 
1282  // find active sourceCollection
1283  $activeSourceCollections = [];
1284  foreach ($conf['sourceCollection.'] as $sourceCollectionKey => $sourceCollectionConfiguration) {
1285  if (substr($sourceCollectionKey, -1) === '.') {
1286  if (empty($sourceCollectionConfiguration['if.']) || $this->checkIf($sourceCollectionConfiguration['if.'])) {
1287  $activeSourceCollections[] = $sourceCollectionConfiguration;
1288  }
1289  }
1290  }
1291 
1292  // apply option split to configurations
1293  $tsfe = $this->getTypoScriptFrontendController();
1294  $srcLayoutOptionSplitted = $tsfe->tmpl->splitConfArray($conf['layout.'][$layoutKey . '.'], count($activeSourceCollections));
1295 
1296  // render sources
1297  foreach ($activeSourceCollections as $key => $sourceConfiguration) {
1298  $sourceLayout = $this->stdWrap($srcLayoutOptionSplitted[$key]['source'], $srcLayoutOptionSplitted[$key]['source.']);
1299 
1300  $sourceRenderConfiguration = [
1301  'file' => $file,
1302  'file.' => $conf['file.']
1303  ];
1304 
1305  if (isset($sourceConfiguration['quality']) || isset($sourceConfiguration['quality.'])) {
1306  $imageQuality = isset($sourceConfiguration['quality']) ? $sourceConfiguration['quality'] : '';
1307  if (isset($sourceConfiguration['quality.'])) {
1308  $imageQuality = $this->stdWrap($sourceConfiguration['quality'], $sourceConfiguration['quality.']);
1309  }
1310  if ($imageQuality) {
1311  $sourceRenderConfiguration['file.']['params'] = '-quality ' . (int)$imageQuality;
1312  }
1313  }
1314 
1315  if (isset($sourceConfiguration['pixelDensity'])) {
1316  $pixelDensity = (int)$this->stdWrap($sourceConfiguration['pixelDensity'], $sourceConfiguration['pixelDensity.']);
1317  } else {
1318  $pixelDensity = 1;
1319  }
1320  $dimensionKeys = ['width', 'height', 'maxW', 'minW', 'maxH', 'minH'];
1321  foreach ($dimensionKeys as $dimensionKey) {
1322  $dimension = $this->stdWrap($sourceConfiguration[$dimensionKey], $sourceConfiguration[$dimensionKey . '.']);
1323  if (!$dimension) {
1324  $dimension = $this->stdWrap($conf['file.'][$dimensionKey], $conf['file.'][$dimensionKey . '.']);
1325  }
1326  if ($dimension) {
1327  if (strstr($dimension, 'c') !== false && ($dimensionKey === 'width' || $dimensionKey === 'height')) {
1328  $dimensionParts = explode('c', $dimension, 2);
1329  $dimension = ((int)$dimensionParts[0] * $pixelDensity) . 'c';
1330  if ($dimensionParts[1]) {
1331  $dimension .= $dimensionParts[1];
1332  }
1333  } else {
1334  $dimension = (int)$dimension * $pixelDensity;
1335  }
1336  $sourceRenderConfiguration['file.'][$dimensionKey] = $dimension;
1337  // Remove the stdWrap properties for dimension as they have been processed already above.
1338  unset($sourceRenderConfiguration['file.'][$dimensionKey . '.']);
1339  }
1340  }
1341  $sourceInfo = $this->getImgResource($sourceRenderConfiguration['file'], $sourceRenderConfiguration['file.']);
1342  if ($sourceInfo) {
1343  $sourceConfiguration['width'] = $sourceInfo[0];
1344  $sourceConfiguration['height'] = $sourceInfo[1];
1345  $urlPrefix = '';
1346  if (parse_url($sourceInfo[3], PHP_URL_HOST) === null) {
1347  $urlPrefix = $tsfe->absRefPrefix;
1348  }
1349  $sourceConfiguration['src'] = htmlspecialchars($urlPrefix . $sourceInfo[3]);
1350  $sourceConfiguration['selfClosingTagSlash'] = !empty($tsfe->xhtmlDoctype) ? ' /' : '';
1351 
1352  $oneSourceCollection = $this->substituteMarkerArray($sourceLayout, $sourceConfiguration, '###|###', true, true);
1353 
1354  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImageSourceCollection'])) {
1355  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImageSourceCollection'] as $classData) {
1356  $hookObject = GeneralUtility::getUserObj($classData);
1357  if (!$hookObject instanceof ContentObjectOneSourceCollectionHookInterface) {
1358  throw new \UnexpectedValueException(
1359  '$hookObject must implement interface ' . ContentObjectOneSourceCollectionHookInterface::class,
1360  1380007853
1361  );
1362  }
1363  $oneSourceCollection = $hookObject->getOneSourceCollection((array)$sourceRenderConfiguration, (array)$sourceConfiguration, $oneSourceCollection, $this);
1364  }
1365  }
1366 
1367  $sourceCollection .= $oneSourceCollection;
1368  }
1369  }
1370  }
1371  return $sourceCollection;
1372  }
1373 
1383  public function imageLinkWrap($string, $imageFile, $conf)
1384  {
1385  $string = (string)$string;
1386  $enable = isset($conf['enable.']) ? $this->stdWrap($conf['enable'], $conf['enable.']) : $conf['enable'];
1387  if (!$enable) {
1388  return $string;
1389  }
1390  $content = (string)$this->typoLink($string, $conf['typolink.']);
1391  if (isset($conf['file.'])) {
1392  $imageFile = $this->stdWrap($imageFile, $conf['file.']);
1393  }
1394 
1395  if ($imageFile instanceof File) {
1396  $file = $imageFile;
1397  } elseif ($imageFile instanceof FileReference) {
1398  $file = $imageFile->getOriginalFile();
1399  } else {
1400  if (MathUtility::canBeInterpretedAsInteger($imageFile)) {
1401  $file = ResourceFactory::getInstance()->getFileObject((int)$imageFile);
1402  } else {
1403  $file = ResourceFactory::getInstance()->getFileObjectFromCombinedIdentifier($imageFile);
1404  }
1405  }
1406 
1407  // Create imageFileLink if not created with typolink
1408  if ($content === $string) {
1409  $parameterNames = ['width', 'height', 'effects', 'bodyTag', 'title', 'wrap', 'crop'];
1410  $parameters = [];
1411  $sample = isset($conf['sample.']) ? $this->stdWrap($conf['sample'], $conf['sample.']) : $conf['sample'];
1412  if ($sample) {
1413  $parameters['sample'] = 1;
1414  }
1415  foreach ($parameterNames as $parameterName) {
1416  if (isset($conf[$parameterName . '.'])) {
1417  $conf[$parameterName] = $this->stdWrap($conf[$parameterName], $conf[$parameterName . '.']);
1418  }
1419  if (isset($conf[$parameterName]) && $conf[$parameterName]) {
1420  $parameters[$parameterName] = $conf[$parameterName];
1421  }
1422  }
1423  $parametersEncoded = base64_encode(serialize($parameters));
1424  $hmac = GeneralUtility::hmac(implode('|', [$file->getUid(), $parametersEncoded]));
1425  $params = '&md5=' . $hmac;
1426  foreach (str_split($parametersEncoded, 64) as $index => $chunk) {
1427  $params .= '&parameters' . rawurlencode('[') . $index . rawurlencode(']') . '=' . rawurlencode($chunk);
1428  }
1429  $url = $this->getTypoScriptFrontendController()->absRefPrefix . 'index.php?eID=tx_cms_showpic&file=' . $file->getUid() . $params;
1430  $directImageLink = isset($conf['directImageLink.']) ? $this->stdWrap($conf['directImageLink'], $conf['directImageLink.']) : $conf['directImageLink'];
1431  if ($directImageLink) {
1432  $imgResourceConf = [
1433  'file' => $imageFile,
1434  'file.' => $conf
1435  ];
1436  $url = $this->cObjGetSingle('IMG_RESOURCE', $imgResourceConf);
1437  if (!$url) {
1438  // If no imagemagick / gm is available
1439  $url = $imageFile;
1440  }
1441  }
1442  // Create TARGET-attribute only if the right doctype is used
1443  $target = '';
1444  $xhtmlDocType = $this->getTypoScriptFrontendController()->xhtmlDoctype;
1445  if ($xhtmlDocType !== 'xhtml_strict' && $xhtmlDocType !== 'xhtml_11') {
1446  $target = isset($conf['target.'])
1447  ? (string)$this->stdWrap($conf['target'], $conf['target.'])
1448  : (string)$conf['target'];
1449  if ($target === '') {
1450  $target = 'thePicture';
1451  }
1452  }
1453  $a1 = '';
1454  $a2 = '';
1455  $conf['JSwindow'] = isset($conf['JSwindow.']) ? $this->stdWrap($conf['JSwindow'], $conf['JSwindow.']) : $conf['JSwindow'];
1456  if ($conf['JSwindow']) {
1457  if ($conf['JSwindow.']['altUrl'] || $conf['JSwindow.']['altUrl.']) {
1458  $altUrl = isset($conf['JSwindow.']['altUrl.']) ? $this->stdWrap($conf['JSwindow.']['altUrl'], $conf['JSwindow.']['altUrl.']) : $conf['JSwindow.']['altUrl'];
1459  if ($altUrl) {
1460  $url = $altUrl . ($conf['JSwindow.']['altUrl_noDefaultParams'] ? '' : '?file=' . rawurlencode($imageFile) . $params);
1461  }
1462  }
1463 
1464  $processedFile = $file->process('Image.CropScaleMask', $conf);
1465  $JSwindowExpand = isset($conf['JSwindow.']['expand.']) ? $this->stdWrap($conf['JSwindow.']['expand'], $conf['JSwindow.']['expand.']) : $conf['JSwindow.']['expand'];
1466  $offset = GeneralUtility::intExplode(',', $JSwindowExpand . ',');
1467  $newWindow = isset($conf['JSwindow.']['newWindow.']) ? $this->stdWrap($conf['JSwindow.']['newWindow'], $conf['JSwindow.']['newWindow.']) : $conf['JSwindow.']['newWindow'];
1468  $onClick = 'openPic('
1469  . GeneralUtility::quoteJSvalue($this->getTypoScriptFrontendController()->baseUrlWrap($url)) . ','
1470  . '\'' . ($newWindow ? md5($url) : 'thePicture') . '\','
1471  . GeneralUtility::quoteJSvalue('width=' . ($processedFile->getProperty('width') + $offset[0])
1472  . ',height=' . ($processedFile->getProperty('height') + $offset[1]) . ',status=0,menubar=0')
1473  . '); return false;';
1474  $a1 = '<a href="' . htmlspecialchars($url) . '"'
1475  . ' onclick="' . htmlspecialchars($onClick) . '"'
1476  . ($target !== '' ? ' target="' . htmlspecialchars($target) . '"' : '')
1477  . $this->getTypoScriptFrontendController()->ATagParams . '>';
1478  $a2 = '</a>';
1479  $this->getTypoScriptFrontendController()->setJS('openPic');
1480  } else {
1481  $conf['linkParams.']['parameter'] = $url;
1482  $string = $this->typoLink($string, $conf['linkParams.']);
1483  }
1484  if (isset($conf['stdWrap.'])) {
1485  $string = $this->stdWrap($string, $conf['stdWrap.']);
1486  }
1487  $content = $a1 . $string . $a2;
1488  }
1489  return $content;
1490  }
1491 
1501  public function fileResource($fName, $addParams = 'alt="" title=""')
1502  {
1503  GeneralUtility::logDeprecatedFunction();
1504  $tsfe = $this->getTypoScriptFrontendController();
1505  $incFile = $tsfe->tmpl->getFileName($fName);
1506  if ($incFile && file_exists($incFile)) {
1507  $fileInfo = GeneralUtility::split_fileref($incFile);
1508  $extension = $fileInfo['fileext'];
1509  if ($extension === 'jpg' || $extension === 'jpeg' || $extension === 'gif' || $extension === 'png') {
1510  $imgFile = $incFile;
1511  $imgInfo = @getimagesize($imgFile);
1512  return '<img src="' . htmlspecialchars($tsfe->absRefPrefix . $imgFile) . '" width="' . (int)$imgInfo[0] . '" height="' . (int)$imgInfo[1] . '"' . $this->getBorderAttr(' border="0"') . ' ' . $addParams . ' />';
1513  } elseif (filesize($incFile) < 1024 * 1024) {
1514  return file_get_contents($incFile);
1515  }
1516  }
1517  return '';
1518  }
1519 
1529  public function lastChanged($tstamp)
1530  {
1531  $tstamp = (int)$tstamp;
1532  $tsfe = $this->getTypoScriptFrontendController();
1533  if ($tstamp > (int)$tsfe->register['SYS_LASTCHANGED']) {
1534  $tsfe->register['SYS_LASTCHANGED'] = $tstamp;
1535  }
1536  }
1537 
1547  public function linkWrap($content, $wrap)
1548  {
1549  $wrapArr = explode('|', $wrap);
1550  if (preg_match('/\\{([0-9]*)\\}/', $wrapArr[0], $reg)) {
1551  if ($uid = $this->getTypoScriptFrontendController()->tmpl->rootLine[$reg[1]]['uid']) {
1552  $wrapArr[0] = str_replace($reg[0], $uid, $wrapArr[0]);
1553  }
1554  }
1555  return trim($wrapArr[0]) . $content . trim($wrapArr[1]);
1556  }
1557 
1567  public function getAltParam($conf, $longDesc = true)
1568  {
1569  $altText = isset($conf['altText.']) ? trim($this->stdWrap($conf['altText'], $conf['altText.'])) : trim($conf['altText']);
1570  $titleText = isset($conf['titleText.']) ? trim($this->stdWrap($conf['titleText'], $conf['titleText.'])) : trim($conf['titleText']);
1571  if (isset($conf['longdescURL.']) && $this->getTypoScriptFrontendController()->config['config']['doctype'] != 'html5') {
1572  $longDescUrl = $this->typoLink_URL($conf['longdescURL.']);
1573  } else {
1574  $longDescUrl = trim($conf['longdescURL']);
1575  }
1576  $longDescUrl = strip_tags($longDescUrl);
1577 
1578  // "alt":
1579  $altParam = ' alt="' . htmlspecialchars($altText) . '"';
1580  // "title":
1581  $emptyTitleHandling = isset($conf['emptyTitleHandling.']) ? $this->stdWrap($conf['emptyTitleHandling'], $conf['emptyTitleHandling.']) : $conf['emptyTitleHandling'];
1582  // Choices: 'keepEmpty' | 'useAlt' | 'removeAttr'
1583  if ($titleText || $emptyTitleHandling === 'keepEmpty') {
1584  $altParam .= ' title="' . htmlspecialchars($titleText) . '"';
1585  } elseif (!$titleText && $emptyTitleHandling === 'useAlt') {
1586  $altParam .= ' title="' . htmlspecialchars($altText) . '"';
1587  }
1588  // "longDesc" URL
1589  if ($longDesc && !empty($longDescUrl)) {
1590  $altParam .= ' longdesc="' . htmlspecialchars($longDescUrl) . '"';
1591  }
1592  return $altParam;
1593  }
1594 
1604  public function getATagParams($conf, $addGlobal = 1)
1605  {
1606  $aTagParams = '';
1607  if ($conf['ATagParams.']) {
1608  $aTagParams = ' ' . $this->stdWrap($conf['ATagParams'], $conf['ATagParams.']);
1609  } elseif ($conf['ATagParams']) {
1610  $aTagParams = ' ' . $conf['ATagParams'];
1611  }
1612  if ($addGlobal) {
1613  $aTagParams = ' ' . trim($this->getTypoScriptFrontendController()->ATagParams . $aTagParams);
1614  }
1615  // Extend params
1616  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'])) {
1617  $_params = [
1618  'conf' => &$conf,
1619  'aTagParams' => &$aTagParams
1620  ];
1621  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getATagParamsPostProc'] as $objRef) {
1622  $processor =& GeneralUtility::getUserObj($objRef);
1623  $aTagParams = $processor->process($_params, $this);
1624  }
1625  }
1626 
1627  $aTagParams = trim($aTagParams);
1628  if (!empty($aTagParams)) {
1629  $aTagParams = ' ' . $aTagParams;
1630  }
1631 
1632  return $aTagParams;
1633  }
1634 
1643  public function extLinkATagParams($URL, $TYPE)
1644  {
1645  $out = '';
1646  if ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['extLinkATagParamsHandler']) {
1647  $extLinkATagParamsHandler = GeneralUtility::getUserObj($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['extLinkATagParamsHandler']);
1648  if (method_exists($extLinkATagParamsHandler, 'main')) {
1649  $out .= trim($extLinkATagParamsHandler->main($URL, $TYPE, $this));
1650  }
1651  }
1652  return trim($out) ? ' ' . trim($out) : '';
1653  }
1654 
1655  /***********************************************
1656  *
1657  * HTML template processing functions
1658  *
1659  ***********************************************/
1678  public function getSubpart($content, $marker)
1679  {
1680  return $this->templateService->getSubpart($content, $marker);
1681  }
1682 
1695  public function substituteSubpart($content, $marker, $subpartContent, $recursive = 1)
1696  {
1697  return $this->templateService->substituteSubpart($content, $marker, $subpartContent, $recursive);
1698  }
1699 
1707  public function substituteSubpartArray($content, array $subpartsContent)
1708  {
1709  return $this->templateService->substituteSubpartArray($content, $subpartsContent);
1710  }
1711 
1722  public function substituteMarker($content, $marker, $markContent)
1723  {
1724  return $this->templateService->substituteMarker($content, $marker, $markContent);
1725  }
1726 
1759  public function substituteMarkerArrayCached($content, array $markContentArray = null, array $subpartContentArray = null, array $wrappedSubpartContentArray = null)
1760  {
1761  $timeTracker = $this->getTimeTracker();
1762  $timeTracker->push('substituteMarkerArrayCached');
1763  // If not arrays then set them
1764  if (is_null($markContentArray)) {
1765  // Plain markers
1766  $markContentArray = [];
1767  }
1768  if (is_null($subpartContentArray)) {
1769  // Subparts being directly substituted
1770  $subpartContentArray = [];
1771  }
1772  if (is_null($wrappedSubpartContentArray)) {
1773  // Subparts being wrapped
1774  $wrappedSubpartContentArray = [];
1775  }
1776  // Finding keys and check hash:
1777  $sPkeys = array_keys($subpartContentArray);
1778  $wPkeys = array_keys($wrappedSubpartContentArray);
1779  $keysToReplace = array_merge(array_keys($markContentArray), $sPkeys, $wPkeys);
1780  if (empty($keysToReplace)) {
1781  $timeTracker->pull();
1782  return $content;
1783  }
1784  asort($keysToReplace);
1785  $storeKey = md5('substituteMarkerArrayCached_storeKey:' . serialize([$content, $keysToReplace]));
1786  if ($this->substMarkerCache[$storeKey]) {
1787  $storeArr = $this->substMarkerCache[$storeKey];
1788  $timeTracker->setTSlogMessage('Cached', 0);
1789  } else {
1790  $storeArrDat = $this->getTypoScriptFrontendController()->sys_page->getHash($storeKey);
1791  if (is_array($storeArrDat)) {
1792  $storeArr = $storeArrDat;
1793  // Setting cache:
1794  $this->substMarkerCache[$storeKey] = $storeArr;
1795  $timeTracker->setTSlogMessage('Cached from DB', 0);
1796  } else {
1797  // Finding subparts and substituting them with the subpart as a marker
1798  foreach ($sPkeys as $sPK) {
1799  $content = $this->substituteSubpart($content, $sPK, $sPK);
1800  }
1801  // Finding subparts and wrapping them with markers
1802  foreach ($wPkeys as $wPK) {
1803  $content = $this->substituteSubpart($content, $wPK, [
1804  $wPK,
1805  $wPK
1806  ]);
1807  }
1808 
1809  $storeArr = [];
1810  // search all markers in the content
1811  $result = preg_match_all('/###([^#](?:[^#]*+|#{1,2}[^#])+)###/', $content, $markersInContent);
1812  if ($result !== false && !empty($markersInContent[1])) {
1813  $keysToReplaceFlipped = array_flip($keysToReplace);
1814  $regexKeys = [];
1815  $wrappedKeys = [];
1816  // Traverse keys and quote them for reg ex.
1817  foreach ($markersInContent[1] as $key) {
1818  if (isset($keysToReplaceFlipped['###' . $key . '###'])) {
1819  $regexKeys[] = preg_quote($key, '/');
1820  $wrappedKeys[] = '###' . $key . '###';
1821  }
1822  }
1823  $regex = '/###(?:' . implode('|', $regexKeys) . ')###/';
1824  $storeArr['c'] = preg_split($regex, $content); // contains all content parts around markers
1825  $storeArr['k'] = $wrappedKeys; // contains all markers incl. ###
1826  // Setting cache:
1827  $this->substMarkerCache[$storeKey] = $storeArr;
1828  // Storing the cached data:
1829  $this->getTypoScriptFrontendController()->sys_page->storeHash($storeKey, $storeArr, 'substMarkArrayCached');
1830  }
1831  $timeTracker->setTSlogMessage('Parsing', 0);
1832  }
1833  }
1834  if (!empty($storeArr['k']) && is_array($storeArr['k'])) {
1835  // Substitution/Merging:
1836  // Merging content types together, resetting
1837  $valueArr = array_merge($markContentArray, $subpartContentArray, $wrappedSubpartContentArray);
1838  $wSCA_reg = [];
1839  $content = '';
1840  // Traversing the keyList array and merging the static and dynamic content
1841  foreach ($storeArr['k'] as $n => $keyN) {
1842  // add content before marker
1843  $content .= $storeArr['c'][$n];
1844  if (!is_array($valueArr[$keyN])) {
1845  // fetch marker replacement from $markContentArray or $subpartContentArray
1846  $content .= $valueArr[$keyN];
1847  } else {
1848  if (!isset($wSCA_reg[$keyN])) {
1849  $wSCA_reg[$keyN] = 0;
1850  }
1851  // fetch marker replacement from $wrappedSubpartContentArray
1852  $content .= $valueArr[$keyN][$wSCA_reg[$keyN] % 2];
1853  $wSCA_reg[$keyN]++;
1854  }
1855  }
1856  // add remaining content
1857  $content .= $storeArr['c'][count($storeArr['k'])];
1858  }
1859  $timeTracker->pull();
1860  return $content;
1861  }
1862 
1882  public function substituteMarkerArray($content, array $markContentArray, $wrap = '', $uppercase = false, $deleteUnused = false)
1883  {
1884  return $this->templateService->substituteMarkerArray($content, $markContentArray, $wrap, $uppercase, $deleteUnused);
1885  }
1886 
1895  public function substituteMarkerInObject(&$tree, array $markContentArray)
1896  {
1897  if (is_array($tree)) {
1898  foreach ($tree as $key => $value) {
1899  $this->substituteMarkerInObject($tree[$key], $markContentArray);
1900  }
1901  } else {
1902  $tree = $this->substituteMarkerArray($tree, $markContentArray);
1903  }
1904  return $tree;
1905  }
1906 
1917  public function substituteMarkerAndSubpartArrayRecursive($content, array $markersAndSubparts, $wrap = '', $uppercase = false, $deleteUnused = false)
1918  {
1919  return $this->templateService->substituteMarkerAndSubpartArrayRecursive($content, $markersAndSubparts, $wrap, $uppercase, $deleteUnused);
1920  }
1921 
1934  public function fillInMarkerArray(array $markContentArray, array $row, $fieldList = '', $nl2br = true, $prefix = 'FIELD_', $HSC = false)
1935  {
1936  $tsfe = $this->getTypoScriptFrontendController();
1937  if ($fieldList) {
1938  $fArr = GeneralUtility::trimExplode(',', $fieldList, true);
1939  foreach ($fArr as $field) {
1940  $markContentArray['###' . $prefix . $field . '###'] = $nl2br ? nl2br($row[$field], !empty($tsfe->xhtmlDoctype)) : $row[$field];
1941  }
1942  } else {
1943  if (is_array($row)) {
1944  foreach ($row as $field => $value) {
1945  if (!MathUtility::canBeInterpretedAsInteger($field)) {
1946  if ($HSC) {
1947  $value = htmlspecialchars($value);
1948  }
1949  $markContentArray['###' . $prefix . $field . '###'] = $nl2br ? nl2br($value, !empty($tsfe->xhtmlDoctype)) : $value;
1950  }
1951  }
1952  }
1953  }
1954  return $markContentArray;
1955  }
1956 
1962  public function setCurrentFile($fileObject)
1963  {
1964  $this->currentFile = $fileObject;
1965  }
1966 
1972  public function getCurrentFile()
1973  {
1974  return $this->currentFile;
1975  }
1976 
1977  /***********************************************
1978  *
1979  * "stdWrap" + sub functions
1980  *
1981  ***********************************************/
1993  public function stdWrap($content = '', $conf = [])
1994  {
1995  $content = (string)$content;
1996  // If there is any hook object, activate all of the process and override functions.
1997  // The hook interface ContentObjectStdWrapHookInterface takes care that all 4 methods exist.
1998  if ($this->stdWrapHookObjects) {
1999  $conf['stdWrapPreProcess'] = 1;
2000  $conf['stdWrapOverride'] = 1;
2001  $conf['stdWrapProcess'] = 1;
2002  $conf['stdWrapPostProcess'] = 1;
2003  }
2004 
2005  if (!is_array($conf) || !$conf) {
2006  return $content;
2007  }
2008 
2009  // Cache handling
2010  if (is_array($conf['cache.'])) {
2011  $conf['cache.']['key'] = $this->stdWrap($conf['cache.']['key'], $conf['cache.']['key.']);
2012  $conf['cache.']['tags'] = $this->stdWrap($conf['cache.']['tags'], $conf['cache.']['tags.']);
2013  $conf['cache.']['lifetime'] = $this->stdWrap($conf['cache.']['lifetime'], $conf['cache.']['lifetime.']);
2014  $conf['cacheRead'] = 1;
2015  $conf['cacheStore'] = 1;
2016  }
2017  // The configuration is sorted and filtered by intersection with the defined stdWrapOrder.
2018  $sortedConf = array_keys(array_intersect_key($this->stdWrapOrder, $conf));
2019  // Functions types that should not make use of nested stdWrap function calls to avoid conflicts with internal TypoScript used by these functions
2020  $stdWrapDisabledFunctionTypes = 'cObject,functionName,stdWrap';
2021  // Additional Array to check whether a function has already been executed
2022  $isExecuted = [];
2023  // Additional switch to make sure 'required', 'if' and 'fieldRequired'
2024  // will still stop rendering immediately in case they return FALSE
2025  $this->stdWrapRecursionLevel++;
2026  $this->stopRendering[$this->stdWrapRecursionLevel] = false;
2027  // execute each function in the predefined order
2028  foreach ($sortedConf as $stdWrapName) {
2029  // eliminate the second key of a pair 'key'|'key.' to make sure functions get called only once and check if rendering has been stopped
2030  if (!$isExecuted[$stdWrapName] && !$this->stopRendering[$this->stdWrapRecursionLevel]) {
2031  $functionName = rtrim($stdWrapName, '.');
2032  $functionProperties = $functionName . '.';
2033  $functionType = $this->stdWrapOrder[$functionName];
2034  // If there is any code on the next level, check if it contains "official" stdWrap functions
2035  // if yes, execute them first - will make each function stdWrap aware
2036  // so additional stdWrap calls within the functions can be removed, since the result will be the same
2037  if (!empty($conf[$functionProperties]) && !GeneralUtility::inList($stdWrapDisabledFunctionTypes, $functionType)) {
2038  if (array_intersect_key($this->stdWrapOrder, $conf[$functionProperties])) {
2039  $conf[$functionName] = $this->stdWrap($conf[$functionName], $conf[$functionProperties]);
2040  }
2041  }
2042  // Check if key is still containing something, since it might have been changed by next level stdWrap before
2043  if ((isset($conf[$functionName]) || $conf[$functionProperties]) && ($functionType !== 'boolean' || $conf[$functionName])) {
2044  // Get just that part of $conf that is needed for the particular function
2045  $singleConf = [
2046  $functionName => $conf[$functionName],
2047  $functionProperties => $conf[$functionProperties]
2048  ];
2049  // In this special case 'spaceBefore' and 'spaceAfter' need additional stuff from 'space.''
2050  if ($functionName === 'spaceBefore' || $functionName === 'spaceAfter') {
2051  $singleConf['space.'] = $conf['space.'];
2052  }
2053  // Hand over the whole $conf array to the stdWrapHookObjects
2054  if ($functionType === 'hook') {
2055  $singleConf = $conf;
2056  }
2057  // Add both keys - with and without the dot - to the set of executed functions
2058  $isExecuted[$functionName] = true;
2059  $isExecuted[$functionProperties] = true;
2060  // Call the function with the prefix stdWrap_ to make sure nobody can execute functions just by adding their name to the TS Array
2061  $functionName = 'stdWrap_' . $functionName;
2062  $content = $this->{$functionName}($content, $singleConf);
2063  } elseif ($functionType === 'boolean' && !$conf[$functionName]) {
2064  $isExecuted[$functionName] = true;
2065  $isExecuted[$functionProperties] = true;
2066  }
2067  }
2068  }
2069  unset($this->stopRendering[$this->stdWrapRecursionLevel]);
2070  $this->stdWrapRecursionLevel--;
2071 
2072  return $content;
2073  }
2074 
2083  public function stdWrapValue($key, array $config, $defaultValue = '')
2084  {
2085  if (isset($config[$key])) {
2086  if (!isset($config[$key . '.'])) {
2087  return $config[$key];
2088  }
2089  } elseif (isset($config[$key . '.'])) {
2090  $config[$key] = '';
2091  } else {
2092  return $defaultValue;
2093  }
2094  $stdWrapped = $this->stdWrap($config[$key], $config[$key . '.']);
2095  return $stdWrapped ?: $defaultValue;
2096  }
2097 
2107  public function stdWrap_stdWrapPreProcess($content = '', $conf = [])
2108  {
2109  foreach ($this->stdWrapHookObjects as $hookObject) {
2111  $content = $hookObject->stdWrapPreProcess($content, $conf, $this);
2112  }
2113  return $content;
2114  }
2115 
2123  public function stdWrap_cacheRead($content = '', $conf = [])
2124  {
2125  if (!isset($conf['cache.'])) {
2126  return $content;
2127  }
2128  $result = $this->getFromCache($conf['cache.']);
2129  return $result === false ? $content : $result;
2130  }
2131 
2139  public function stdWrap_addPageCacheTags($content = '', $conf = [])
2140  {
2141  $tags = isset($conf['addPageCacheTags.'])
2142  ? $this->stdWrap($conf['addPageCacheTags'], $conf['addPageCacheTags.'])
2143  : $conf['addPageCacheTags'];
2144  if (!empty($tags)) {
2145  $cacheTags = GeneralUtility::trimExplode(',', $tags, true);
2146  $this->getTypoScriptFrontendController()->addCacheTags($cacheTags);
2147  }
2148  return $content;
2149  }
2150 
2158  public function stdWrap_setContentToCurrent($content = '')
2159  {
2160  $this->data[$this->currentValKey] = $content;
2161  return $content;
2162  }
2163 
2172  public function stdWrap_setCurrent($content = '', $conf = [])
2173  {
2174  $this->data[$this->currentValKey] = $conf['setCurrent'];
2175  return $content;
2176  }
2177 
2186  public function stdWrap_lang($content = '', $conf = [])
2187  {
2188  $tsfe = $this->getTypoScriptFrontendController();
2189  if (isset($conf['lang.']) && $tsfe->config['config']['language'] && isset($conf['lang.'][$tsfe->config['config']['language']])) {
2190  $content = $conf['lang.'][$tsfe->config['config']['language']];
2191  }
2192  return $content;
2193  }
2194 
2203  public function stdWrap_data($content = '', $conf = [])
2204  {
2205  $content = $this->getData($conf['data'], is_array($this->alternativeData) ? $this->alternativeData : $this->data);
2206  // This must be unset directly after
2207  $this->alternativeData = '';
2208  return $content;
2209  }
2210 
2219  public function stdWrap_field($content = '', $conf = [])
2220  {
2221  return $this->getFieldVal($conf['field']);
2222  }
2223 
2233  public function stdWrap_current($content = '', $conf = [])
2234  {
2235  return $this->data[$this->currentValKey];
2236  }
2237 
2247  public function stdWrap_cObject($content = '', $conf = [])
2248  {
2249  return $this->cObjGetSingle($conf['cObject'], $conf['cObject.'], '/stdWrap/.cObject');
2250  }
2251 
2261  public function stdWrap_numRows($content = '', $conf = [])
2262  {
2263  return $this->numRows($conf['numRows.']);
2264  }
2265 
2274  public function stdWrap_filelist($content = '', $conf = [])
2275  {
2276  return $this->filelist($conf['filelist']);
2277  }
2278 
2287  public function stdWrap_preUserFunc($content = '', $conf = [])
2288  {
2289  return $this->callUserFunction($conf['preUserFunc'], $conf['preUserFunc.'], $content);
2290  }
2291 
2301  public function stdWrap_stdWrapOverride($content = '', $conf = [])
2302  {
2303  foreach ($this->stdWrapHookObjects as $hookObject) {
2305  $content = $hookObject->stdWrapOverride($content, $conf, $this);
2306  }
2307  return $content;
2308  }
2309 
2318  public function stdWrap_override($content = '', $conf = [])
2319  {
2320  if (trim($conf['override'])) {
2321  $content = $conf['override'];
2322  }
2323  return $content;
2324  }
2325 
2335  public function stdWrap_preIfEmptyListNum($content = '', $conf = [])
2336  {
2337  return $this->listNum($content, $conf['preIfEmptyListNum'], $conf['preIfEmptyListNum.']['splitChar']);
2338  }
2339 
2348  public function stdWrap_ifNull($content = '', $conf = [])
2349  {
2350  return $content !== null ? $content : $conf['ifNull'];
2351  }
2352 
2362  public function stdWrap_ifEmpty($content = '', $conf = [])
2363  {
2364  if (!trim($content)) {
2365  $content = $conf['ifEmpty'];
2366  }
2367  return $content;
2368  }
2369 
2379  public function stdWrap_ifBlank($content = '', $conf = [])
2380  {
2381  if (trim($content) === '') {
2382  $content = $conf['ifBlank'];
2383  }
2384  return $content;
2385  }
2386 
2397  public function stdWrap_listNum($content = '', $conf = [])
2398  {
2399  return $this->listNum($content, $conf['listNum'], $conf['listNum.']['splitChar']);
2400  }
2401 
2409  public function stdWrap_trim($content = '')
2410  {
2411  return trim($content);
2412  }
2413 
2422  public function stdWrap_strPad($content = '', $conf = [])
2423  {
2424  // Must specify a length in conf for this to make sense
2425  $length = 0;
2426  // Padding with space is PHP-default
2427  $padWith = ' ';
2428  // Padding on the right side is PHP-default
2429  $padType = STR_PAD_RIGHT;
2430  if (!empty($conf['strPad.']['length'])) {
2431  $length = isset($conf['strPad.']['length.']) ? $this->stdWrap($conf['strPad.']['length'], $conf['strPad.']['length.']) : $conf['strPad.']['length'];
2432  $length = (int)$length;
2433  }
2434  if (isset($conf['strPad.']['padWith']) && (string)$conf['strPad.']['padWith'] !== '') {
2435  $padWith = isset($conf['strPad.']['padWith.']) ? $this->stdWrap($conf['strPad.']['padWith'], $conf['strPad.']['padWith.']) : $conf['strPad.']['padWith'];
2436  }
2437  if (!empty($conf['strPad.']['type'])) {
2438  $type = isset($conf['strPad.']['type.']) ? $this->stdWrap($conf['strPad.']['type'], $conf['strPad.']['type.']) : $conf['strPad.']['type'];
2439  if (strtolower($type) === 'left') {
2440  $padType = STR_PAD_LEFT;
2441  } elseif (strtolower($type) === 'both') {
2442  $padType = STR_PAD_BOTH;
2443  }
2444  }
2445  return str_pad($content, $length, $padWith, $padType);
2446  }
2447 
2459  public function stdWrap_stdWrap($content = '', $conf = [])
2460  {
2461  return $this->stdWrap($content, $conf['stdWrap.']);
2462  }
2463 
2473  public function stdWrap_stdWrapProcess($content = '', $conf = [])
2474  {
2475  foreach ($this->stdWrapHookObjects as $hookObject) {
2477  $content = $hookObject->stdWrapProcess($content, $conf, $this);
2478  }
2479  return $content;
2480  }
2481 
2490  public function stdWrap_required($content = '')
2491  {
2492  if ((string)$content === '') {
2493  $content = '';
2494  $this->stopRendering[$this->stdWrapRecursionLevel] = true;
2495  }
2496  return $content;
2497  }
2498 
2508  public function stdWrap_if($content = '', $conf = [])
2509  {
2510  if (empty($conf['if.']) || $this->checkIf($conf['if.'])) {
2511  return $content;
2512  }
2513  $this->stopRendering[$this->stdWrapRecursionLevel] = true;
2514  return '';
2515  }
2516 
2526  public function stdWrap_fieldRequired($content = '', $conf = [])
2527  {
2528  if (!trim($this->data[$conf['fieldRequired']])) {
2529  $content = '';
2530  $this->stopRendering[$this->stdWrapRecursionLevel] = true;
2531  }
2532  return $content;
2533  }
2534 
2543  public function stdWrap_csConv($content = '', $conf = [])
2544  {
2545  if (!empty($conf['csConv'])) {
2547  $charsetConverter = GeneralUtility::makeInstance(CharsetConverter::class);
2548  $output = $charsetConverter->conv($content, $charsetConverter->parse_charset($conf['csConv']), 'utf-8');
2549  return $output ?: $content;
2550  } else {
2551  return $content;
2552  }
2553  }
2554 
2564  public function stdWrap_parseFunc($content = '', $conf = [])
2565  {
2566  return $this->parseFunc($content, $conf['parseFunc.'], $conf['parseFunc']);
2567  }
2568 
2578  public function stdWrap_HTMLparser($content = '', $conf = [])
2579  {
2580  if (is_array($conf['HTMLparser.'])) {
2581  $content = $this->HTMLparser_TSbridge($content, $conf['HTMLparser.']);
2582  }
2583  return $content;
2584  }
2585 
2595  public function stdWrap_split($content = '', $conf = [])
2596  {
2597  return $this->splitObj($content, $conf['split.']);
2598  }
2599 
2608  public function stdWrap_replacement($content = '', $conf = [])
2609  {
2610  return $this->replacement($content, $conf['replacement.']);
2611  }
2612 
2622  public function stdWrap_prioriCalc($content = '', $conf = [])
2623  {
2624  $content = MathUtility::calculateWithParentheses($content);
2625  if ($conf['prioriCalc'] === 'intval') {
2626  $content = (int)$content;
2627  }
2628  return $content;
2629  }
2630 
2642  public function stdWrap_char($content = '', $conf = [])
2643  {
2644  return chr((int)$conf['char']);
2645  }
2646 
2654  public function stdWrap_intval($content = '')
2655  {
2656  return (int)$content;
2657  }
2658 
2667  public function stdWrap_hash($content = '', array $conf = [])
2668  {
2669  $algorithm = isset($conf['hash.']) ? $this->stdWrap($conf['hash'], $conf['hash.']) : $conf['hash'];
2670  if (function_exists('hash') && in_array($algorithm, hash_algos())) {
2671  return hash($algorithm, $content);
2672  }
2673  // Non-existing hashing algorithm
2674  return '';
2675  }
2676 
2685  public function stdWrap_round($content = '', $conf = [])
2686  {
2687  return $this->round($content, $conf['round.']);
2688  }
2689 
2698  public function stdWrap_numberFormat($content = '', $conf = [])
2699  {
2700  return $this->numberFormat($content, $conf['numberFormat.']);
2701  }
2702 
2710  public function stdWrap_expandList($content = '')
2711  {
2712  return GeneralUtility::expandList($content);
2713  }
2714 
2724  public function stdWrap_date($content = '', $conf = [])
2725  {
2726  // Check for zero length string to mimic default case of date/gmdate.
2727  $content = (string)$content === '' ? $GLOBALS['EXEC_TIME'] : (int)$content;
2728  $content = $conf['date.']['GMT'] ? gmdate($conf['date'], $content) : date($conf['date'], $content);
2729  return $content;
2730  }
2731 
2741  public function stdWrap_strftime($content = '', $conf = [])
2742  {
2743  // Check for zero length string to mimic default case of strtime/gmstrftime
2744  $content = (string)$content === '' ? $GLOBALS['EXEC_TIME'] : (int)$content;
2745  $content = $conf['strftime.']['GMT'] ? gmstrftime($conf['strftime'], $content) : strftime($conf['strftime'], $content);
2746  if (!empty($conf['strftime.']['charset'])) {
2748  $charsetConverter = GeneralUtility::makeInstance(CharsetConverter::class);
2749  $output = $charsetConverter->conv($content, $charsetConverter->parse_charset($conf['strftime.']['charset']), 'utf-8');
2750  return $output ?: $content;
2751  }
2752  return $content;
2753  }
2754 
2763  public function stdWrap_strtotime($content = '', $conf = [])
2764  {
2765  if ($conf['strtotime'] !== '1') {
2766  $content .= ' ' . $conf['strtotime'];
2767  }
2768  return strtotime($content, $GLOBALS['EXEC_TIME']);
2769  }
2770 
2779  public function stdWrap_age($content = '', $conf = [])
2780  {
2781  return $this->calcAge((int)$GLOBALS['EXEC_TIME'] - (int)$content, $conf['age']);
2782  }
2783 
2793  public function stdWrap_case($content = '', $conf = [])
2794  {
2795  return $this->HTMLcaseshift($content, $conf['case']);
2796  }
2797 
2806  public function stdWrap_bytes($content = '', $conf = [])
2807  {
2808  return GeneralUtility::formatSize($content, $conf['bytes.']['labels'], $conf['bytes.']['base']);
2809  }
2810 
2819  public function stdWrap_substring($content = '', $conf = [])
2820  {
2821  return $this->substring($content, $conf['substring']);
2822  }
2823 
2832  public function stdWrap_removeBadHTML($content = '')
2833  {
2834  return $this->removeBadHTML($content);
2835  }
2836 
2845  public function stdWrap_cropHTML($content = '', $conf = [])
2846  {
2847  return $this->cropHTML($content, $conf['cropHTML']);
2848  }
2849 
2857  public function stdWrap_stripHtml($content = '')
2858  {
2859  return strip_tags($content);
2860  }
2861 
2870  public function stdWrap_crop($content = '', $conf = [])
2871  {
2872  return $this->crop($content, $conf['crop']);
2873  }
2874 
2882  public function stdWrap_rawUrlEncode($content = '')
2883  {
2884  return rawurlencode($content);
2885  }
2886 
2896  public function stdWrap_htmlSpecialChars($content = '', $conf = [])
2897  {
2898  if (!empty($conf['htmlSpecialChars.']['preserveEntities'])) {
2899  $content = htmlspecialchars($content, ENT_COMPAT, 'UTF-8', false);
2900  } else {
2901  $content = htmlspecialchars($content);
2902  }
2903  return $content;
2904  }
2905 
2914  public function stdWrap_encodeForJavaScriptValue($content = '')
2915  {
2916  return GeneralUtility::quoteJSvalue($content);
2917  }
2918 
2927  public function stdWrap_doubleBrTag($content = '', $conf = [])
2928  {
2929  return preg_replace('/\R{1,2}[\t\x20]*\R{1,2}/', $conf['doubleBrTag'], $content);
2930  }
2931 
2941  public function stdWrap_br($content = '')
2942  {
2943  return nl2br($content, !empty($this->getTypoScriptFrontendController()->xhtmlDoctype));
2944  }
2945 
2954  public function stdWrap_brTag($content = '', $conf = [])
2955  {
2956  return str_replace(LF, $conf['brTag'], $content);
2957  }
2958 
2968  public function stdWrap_encapsLines($content = '', $conf = [])
2969  {
2970  return $this->encaps_lineSplit($content, $conf['encapsLines.']);
2971  }
2972 
2980  public function stdWrap_keywords($content = '')
2981  {
2982  return $this->keywords($content);
2983  }
2984 
2994  public function stdWrap_innerWrap($content = '', $conf = [])
2995  {
2996  return $this->wrap($content, $conf['innerWrap']);
2997  }
2998 
3008  public function stdWrap_innerWrap2($content = '', $conf = [])
3009  {
3010  return $this->wrap($content, $conf['innerWrap2']);
3011  }
3012 
3023  public function stdWrap_fontTag($content = '', $conf = [])
3024  {
3026  return $this->wrap($content, $conf['fontTag']);
3027  }
3028 
3037  public function stdWrap_addParams($content = '', $conf = [])
3038  {
3039  return $this->addParams($content, $conf['addParams.']);
3040  }
3041 
3051  public function stdWrap_filelink($content = '', $conf = [])
3052  {
3053  return $this->filelink($content, $conf['filelink.']);
3054  }
3055 
3064  public function stdWrap_preCObject($content = '', $conf = [])
3065  {
3066  return $this->cObjGetSingle($conf['preCObject'], $conf['preCObject.'], '/stdWrap/.preCObject') . $content;
3067  }
3068 
3077  public function stdWrap_postCObject($content = '', $conf = [])
3078  {
3079  return $content . $this->cObjGetSingle($conf['postCObject'], $conf['postCObject.'], '/stdWrap/.postCObject');
3080  }
3081 
3091  public function stdWrap_wrapAlign($content = '', $conf = [])
3092  {
3093  $wrapAlign = trim($conf['wrapAlign']);
3094  if ($wrapAlign) {
3095  $content = $this->wrap($content, '<div style="text-align:' . htmlspecialchars($wrapAlign) . ';">|</div>');
3096  }
3097  return $content;
3098  }
3099 
3110  public function stdWrap_typolink($content = '', $conf = [])
3111  {
3112  return $this->typoLink($content, $conf['typolink.']);
3113  }
3114 
3123  public function stdWrap_TCAselectItem($content = '', $conf = [])
3124  {
3125  if (is_array($conf['TCAselectItem.'])) {
3126  $content = $this->TCAlookup($content, $conf['TCAselectItem.']);
3127  }
3128  return $content;
3129  }
3130 
3140  public function stdWrap_spaceBefore($content = '', $conf = [])
3141  {
3142  return $this->wrapSpace($content, trim($conf['spaceBefore']) . '|', $conf['space.']);
3143  }
3144 
3154  public function stdWrap_spaceAfter($content = '', $conf = [])
3155  {
3156  return $this->wrapSpace($content, '|' . trim($conf['spaceAfter']), $conf['space.']);
3157  }
3158 
3169  public function stdWrap_space($content = '', $conf = [])
3170  {
3171  return $this->wrapSpace($content, trim($conf['space']), $conf['space.']);
3172  }
3173 
3186  public function stdWrap_wrap($content = '', $conf = [])
3187  {
3188  return $this->wrap($content, $conf['wrap'], $conf['wrap.']['splitChar'] ? $conf['wrap.']['splitChar'] : '|');
3189  }
3190 
3200  public function stdWrap_noTrimWrap($content = '', $conf = [])
3201  {
3202  $splitChar = isset($conf['noTrimWrap.']['splitChar.'])
3203  ? $this->stdWrap($conf['noTrimWrap.']['splitChar'], $conf['noTrimWrap.']['splitChar.'])
3204  : $conf['noTrimWrap.']['splitChar'];
3205  if ($splitChar === null || $splitChar === '') {
3206  $splitChar = '|';
3207  }
3208  $content = $this->noTrimWrap(
3209  $content,
3210  $conf['noTrimWrap'],
3211  $splitChar
3212  );
3213  return $content;
3214  }
3215 
3225  public function stdWrap_wrap2($content = '', $conf = [])
3226  {
3227  return $this->wrap($content, $conf['wrap2'], $conf['wrap2.']['splitChar'] ? $conf['wrap2.']['splitChar'] : '|');
3228  }
3229 
3239  public function stdWrap_dataWrap($content = '', $conf = [])
3240  {
3241  return $this->dataWrap($content, $conf['dataWrap']);
3242  }
3243 
3252  public function stdWrap_prepend($content = '', $conf = [])
3253  {
3254  return $this->cObjGetSingle($conf['prepend'], $conf['prepend.'], '/stdWrap/.prepend') . $content;
3255  }
3256 
3265  public function stdWrap_append($content = '', $conf = [])
3266  {
3267  return $content . $this->cObjGetSingle($conf['append'], $conf['append.'], '/stdWrap/.append');
3268  }
3269 
3279  public function stdWrap_wrap3($content = '', $conf = [])
3280  {
3281  return $this->wrap($content, $conf['wrap3'], $conf['wrap3.']['splitChar'] ? $conf['wrap3.']['splitChar'] : '|');
3282  }
3283 
3292  public function stdWrap_orderedStdWrap($content = '', $conf = [])
3293  {
3294  $sortedKeysArray = ArrayUtility::filterAndSortByNumericKeys($conf['orderedStdWrap.'], true);
3295  foreach ($sortedKeysArray as $key) {
3296  $content = $this->stdWrap($content, $conf['orderedStdWrap.'][$key . '.']);
3297  }
3298  return $content;
3299  }
3300 
3309  public function stdWrap_outerWrap($content = '', $conf = [])
3310  {
3311  return $this->wrap($content, $conf['outerWrap']);
3312  }
3313 
3321  public function stdWrap_insertData($content = '')
3322  {
3323  return $this->insertData($content);
3324  }
3325 
3334  public function stdWrap_postUserFunc($content = '', $conf = [])
3335  {
3336  return $this->callUserFunction($conf['postUserFunc'], $conf['postUserFunc.'], $content);
3337  }
3338 
3348  public function stdWrap_postUserFuncInt($content = '', $conf = [])
3349  {
3350  $substKey = 'INT_SCRIPT.' . $this->getTypoScriptFrontendController()->uniqueHash();
3351  $this->getTypoScriptFrontendController()->config['INTincScript'][$substKey] = [
3352  'content' => $content,
3353  'postUserFunc' => $conf['postUserFuncInt'],
3354  'conf' => $conf['postUserFuncInt.'],
3355  'type' => 'POSTUSERFUNC',
3356  'cObj' => serialize($this)
3357  ];
3358  $content = '<!--' . $substKey . '-->';
3359  return $content;
3360  }
3361 
3370  public function stdWrap_prefixComment($content = '', $conf = [])
3371  {
3372  if (!$this->getTypoScriptFrontendController()->config['config']['disablePrefixComment'] && !empty($conf['prefixComment'])) {
3373  $content = $this->prefixComment($conf['prefixComment'], [], $content);
3374  }
3375  return $content;
3376  }
3377 
3386  public function stdWrap_editIcons($content = '', $conf = [])
3387  {
3388  if ($this->getTypoScriptFrontendController()->beUserLogin && $conf['editIcons']) {
3389  if (!is_array($conf['editIcons.'])) {
3390  $conf['editIcons.'] = [];
3391  }
3392  $content = $this->editIcons($content, $conf['editIcons'], $conf['editIcons.']);
3393  }
3394  return $content;
3395  }
3396 
3405  public function stdWrap_editPanel($content = '', $conf = [])
3406  {
3407  if ($this->getTypoScriptFrontendController()->beUserLogin) {
3408  $content = $this->editPanel($content, $conf['editPanel.']);
3409  }
3410  return $content;
3411  }
3412 
3420  public function stdWrap_cacheStore($content = '', $conf = [])
3421  {
3422  if (!isset($conf['cache.'])) {
3423  return $content;
3424  }
3425  $key = $this->calculateCacheKey($conf['cache.']);
3426  if (empty($key)) {
3427  return $content;
3428  }
3430  $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash');
3431  $tags = $this->calculateCacheTags($conf['cache.']);
3432  $lifetime = $this->calculateCacheLifetime($conf['cache.']);
3433  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap_cacheStore'])) {
3434  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap_cacheStore'] as $_funcRef) {
3435  $params = [
3436  'key' => $key,
3437  'content' => $content,
3438  'lifetime' => $lifetime,
3439  'tags' => $tags
3440  ];
3441  GeneralUtility::callUserFunction($_funcRef, $params, $this);
3442  }
3443  }
3444  $cacheFrontend->set($key, $content, $tags, $lifetime);
3445  return $content;
3446  }
3447 
3457  public function stdWrap_stdWrapPostProcess($content = '', $conf = [])
3458  {
3459  foreach ($this->stdWrapHookObjects as $hookObject) {
3461  $content = $hookObject->stdWrapPostProcess($content, $conf, $this);
3462  }
3463  return $content;
3464  }
3465 
3473  public function stdWrap_debug($content = '')
3474  {
3475  return '<pre>' . htmlspecialchars($content) . '</pre>';
3476  }
3477 
3486  public function stdWrap_debugFunc($content = '', $conf = [])
3487  {
3488  debug((int)$conf['debugFunc'] === 2 ? [$content] : $content);
3489  return $content;
3490  }
3491 
3499  public function stdWrap_debugData($content = '')
3500  {
3501  debug($this->data, '$cObj->data:');
3502  if (is_array($this->alternativeData)) {
3503  debug($this->alternativeData, '$this->alternativeData');
3504  }
3505  return $content;
3506  }
3507 
3517  public function numRows($conf)
3518  {
3519  $conf['select.']['selectFields'] = 'count(*)';
3520  $statement = $this->exec_getQuery($conf['table'], $conf['select.']);
3521 
3522  return (int)$statement->fetchColumn(0);
3523  }
3524 
3533  public function listNum($content, $listNum, $char)
3534  {
3535  $char = $char ?: ',';
3537  $char = chr($char);
3538  }
3539  $temp = explode($char, $content);
3540  $last = '' . (count($temp) - 1);
3541  // Take a random item if requested
3542  if ($listNum === 'rand') {
3543  $listNum = rand(0, count($temp) - 1);
3544  }
3545  $index = $this->calc(str_ireplace('last', $last, $listNum));
3546  return $temp[$index];
3547  }
3548 
3557  public function checkIf($conf)
3558  {
3559  if (!is_array($conf)) {
3560  return true;
3561  }
3562  if (isset($conf['directReturn'])) {
3563  return (bool)$conf['directReturn'];
3564  }
3565  $flag = true;
3566  if (isset($conf['isNull.'])) {
3567  $isNull = $this->stdWrap('', $conf['isNull.']);
3568  if ($isNull !== null) {
3569  $flag = false;
3570  }
3571  }
3572  if (isset($conf['isTrue']) || isset($conf['isTrue.'])) {
3573  $isTrue = isset($conf['isTrue.']) ? trim($this->stdWrap($conf['isTrue'], $conf['isTrue.'])) : trim($conf['isTrue']);
3574  if (!$isTrue) {
3575  $flag = false;
3576  }
3577  }
3578  if (isset($conf['isFalse']) || isset($conf['isFalse.'])) {
3579  $isFalse = isset($conf['isFalse.']) ? trim($this->stdWrap($conf['isFalse'], $conf['isFalse.'])) : trim($conf['isFalse']);
3580  if ($isFalse) {
3581  $flag = false;
3582  }
3583  }
3584  if (isset($conf['isPositive']) || isset($conf['isPositive.'])) {
3585  $number = isset($conf['isPositive.']) ? $this->calc($this->stdWrap($conf['isPositive'], $conf['isPositive.'])) : $this->calc($conf['isPositive']);
3586  if ($number < 1) {
3587  $flag = false;
3588  }
3589  }
3590  if ($flag) {
3591  $value = isset($conf['value.']) ? trim($this->stdWrap($conf['value'], $conf['value.'])) : trim($conf['value']);
3592  if (isset($conf['isGreaterThan']) || isset($conf['isGreaterThan.'])) {
3593  $number = isset($conf['isGreaterThan.']) ? trim($this->stdWrap($conf['isGreaterThan'], $conf['isGreaterThan.'])) : trim($conf['isGreaterThan']);
3594  if ($number <= $value) {
3595  $flag = false;
3596  }
3597  }
3598  if (isset($conf['isLessThan']) || isset($conf['isLessThan.'])) {
3599  $number = isset($conf['isLessThan.']) ? trim($this->stdWrap($conf['isLessThan'], $conf['isLessThan.'])) : trim($conf['isLessThan']);
3600  if ($number >= $value) {
3601  $flag = false;
3602  }
3603  }
3604  if (isset($conf['equals']) || isset($conf['equals.'])) {
3605  $number = isset($conf['equals.']) ? trim($this->stdWrap($conf['equals'], $conf['equals.'])) : trim($conf['equals']);
3606  if ($number != $value) {
3607  $flag = false;
3608  }
3609  }
3610  if (isset($conf['isInList']) || isset($conf['isInList.'])) {
3611  $number = isset($conf['isInList.']) ? trim($this->stdWrap($conf['isInList'], $conf['isInList.'])) : trim($conf['isInList']);
3612  if (!GeneralUtility::inList($value, $number)) {
3613  $flag = false;
3614  }
3615  }
3616  }
3617  if ($conf['negate']) {
3618  $flag = !$flag;
3619  }
3620  return $flag;
3621  }
3622 
3632  public function filelist($data)
3633  {
3634  $data = trim($data);
3635  if ($data === '') {
3636  return '';
3637  }
3638  $data_arr = explode('|', $data);
3639  // read directory:
3640  // MUST exist!
3641  $path = '';
3642  if ($this->getTypoScriptFrontendController()->lockFilePath) {
3643  // Cleaning name..., only relative paths accepted.
3644  $path = $this->clean_directory($data_arr[0]);
3645  // See if path starts with lockFilePath, the additional '/' is needed because clean_directory gets rid of it
3646  $path = GeneralUtility::isFirstPartOfStr($path . '/', $this->getTypoScriptFrontendController()->lockFilePath) ? $path : '';
3647  }
3648  if (!$path) {
3649  return '';
3650  }
3651  $items = [
3652  'files' => [],
3653  'sorting' => []
3654  ];
3655  $ext_list = strtolower(GeneralUtility::uniqueList($data_arr[1]));
3656  $sorting = trim($data_arr[2]);
3657  // Read dir:
3658  $d = @dir($path);
3659  if (is_object($d)) {
3660  $count = 0;
3661  while ($entry = $d->read()) {
3662  if ($entry != '.' && $entry != '..') {
3663  // Because of odd PHP-error where <br />-tag is sometimes placed after a filename!!
3664  $wholePath = $path . '/' . $entry;
3665  if (file_exists($wholePath) && filetype($wholePath) === 'file') {
3666  $info = GeneralUtility::split_fileref($wholePath);
3667  if (!$ext_list || GeneralUtility::inList($ext_list, $info['fileext'])) {
3668  $items['files'][] = $info['file'];
3669  switch ($sorting) {
3670  case 'name':
3671  $items['sorting'][] = strtolower($info['file']);
3672  break;
3673  case 'size':
3674  $items['sorting'][] = filesize($wholePath);
3675  break;
3676  case 'ext':
3677  $items['sorting'][] = $info['fileext'];
3678  break;
3679  case 'date':
3680  $items['sorting'][] = filectime($wholePath);
3681  break;
3682  case 'mdate':
3683  $items['sorting'][] = filemtime($wholePath);
3684  break;
3685  default:
3686  $items['sorting'][] = $count;
3687  }
3688  $count++;
3689  }
3690  }
3691  }
3692  }
3693  $d->close();
3694  }
3695  // Sort if required
3696  if (!empty($items['sorting'])) {
3697  if (strtolower(trim($data_arr[3])) != 'r') {
3698  asort($items['sorting']);
3699  } else {
3700  arsort($items['sorting']);
3701  }
3702  }
3703  if (!empty($items['files'])) {
3704  // Make list
3705  reset($items['sorting']);
3706  $fullPath = trim($data_arr[4]);
3707  $list_arr = [];
3708  foreach ($items['sorting'] as $key => $v) {
3709  $list_arr[] = $fullPath ? $path . '/' . $items['files'][$key] : $items['files'][$key];
3710  }
3711  return implode(',', $list_arr);
3712  }
3713  return '';
3714  }
3715 
3724  public function clean_directory($theDir)
3725  {
3726  // proceeds if no '//', '..' or '\' is in the $theFile
3727  if (GeneralUtility::validPathStr($theDir)) {
3728  // Removes all dots, slashes and spaces after a path...
3729  $theDir = preg_replace('/[\\/\\. ]*$/', '', $theDir);
3730  if (!GeneralUtility::isAbsPath($theDir) && @is_dir($theDir)) {
3731  return $theDir;
3732  }
3733  }
3734  return '';
3735  }
3736 
3747  public function HTMLparser_TSbridge($theValue, $conf)
3748  {
3749  $htmlParser = GeneralUtility::makeInstance(HtmlParser::class);
3750  $htmlParserCfg = $htmlParser->HTMLparserConfig($conf);
3751  return $htmlParser->HTMLcleaner($theValue, $htmlParserCfg[0], $htmlParserCfg[1], $htmlParserCfg[2], $htmlParserCfg[3]);
3752  }
3753 
3762  public function dataWrap($content, $wrap)
3763  {
3764  return $this->wrap($content, $this->insertData($wrap));
3765  }
3766 
3775  public function insertData($str)
3776  {
3777  $inside = 0;
3778  $newVal = '';
3779  $pointer = 0;
3780  $totalLen = strlen($str);
3781  do {
3782  if (!$inside) {
3783  $len = strcspn(substr($str, $pointer), '{');
3784  $newVal .= substr($str, $pointer, $len);
3785  $inside = true;
3786  } else {
3787  $len = strcspn(substr($str, $pointer), '}') + 1;
3788  $newVal .= $this->getData(substr($str, $pointer + 1, $len - 2), $this->data);
3789  $inside = false;
3790  }
3791  $pointer += $len;
3792  } while ($pointer < $totalLen);
3793  return $newVal;
3794  }
3795 
3806  public function prefixComment($str, $conf, $content)
3807  {
3808  if (empty($str)) {
3809  return $content;
3810  }
3811  $parts = explode('|', $str);
3812  $indent = (int)$parts[0];
3813  $comment = htmlspecialchars($this->insertData($parts[1]));
3814  $output = LF
3815  . str_pad('', $indent, TAB) . '<!-- ' . $comment . ' [begin] -->' . LF
3816  . str_pad('', ($indent + 1), TAB) . $content . LF
3817  . str_pad('', $indent, TAB) . '<!-- ' . $comment . ' [end] -->' . LF
3818  . str_pad('', ($indent + 1), TAB);
3819  return $output;
3820  }
3821 
3831  public function substring($content, $options)
3832  {
3833  $options = GeneralUtility::intExplode(',', $options . ',');
3834  if ($options[1]) {
3835  return mb_substr($content, $options[0], $options[1], 'utf-8');
3836  } else {
3837  return mb_substr($content, $options[0], null, 'utf-8');
3838  }
3839  }
3840 
3850  public function crop($content, $options)
3851  {
3852  $options = explode('|', $options);
3853  $chars = (int)$options[0];
3854  $afterstring = trim($options[1]);
3855  $crop2space = trim($options[2]);
3856  if ($chars) {
3857  if (mb_strlen($content, 'utf-8') > abs($chars)) {
3858  $truncatePosition = false;
3859  if ($chars < 0) {
3860  $content = mb_substr($content, $chars, null, 'utf-8');
3861  if ($crop2space) {
3862  $truncatePosition = strpos($content, ' ');
3863  }
3864  $content = $truncatePosition ? $afterstring . substr($content, $truncatePosition) : $afterstring . $content;
3865  } else {
3866  $content = mb_substr($content, 0, $chars, 'utf-8');
3867  if ($crop2space) {
3868  $truncatePosition = strrpos($content, ' ');
3869  }
3870  $content = $truncatePosition ? substr($content, 0, $truncatePosition) . $afterstring : $content . $afterstring;
3871  }
3872  }
3873  }
3874  return $content;
3875  }
3876 
3890  public function cropHTML($content, $options)
3891  {
3892  $options = explode('|', $options);
3893  $chars = (int)$options[0];
3894  $absChars = abs($chars);
3895  $replacementForEllipsis = trim($options[1]);
3896  $crop2space = trim($options[2]) === '1';
3897  // Split $content into an array(even items in the array are outside the tags, odd numbers are tag-blocks).
3898  $tags = 'a|abbr|address|area|article|aside|audio|b|bdi|bdo|blockquote|body|br|button|caption|cite|code|col|colgroup|data|datalist|dd|del|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|font|footer|form|h1|h2|h3|h4|h5|h6|header|hr|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|main|map|mark|meter|nav|object|ol|optgroup|option|output|p|param|pre|progress|q|rb|rp|rt|rtc|ruby|s|samp|section|select|small|source|span|strong|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|tr|track|u|ul|ut|var|video|wbr';
3899  $tagsRegEx = '
3900  (
3901  (?:
3902  <!--.*?--> # a comment
3903  |
3904  <canvas[^>]*>.*?</canvas> # a canvas tag
3905  |
3906  <script[^>]*>.*?</script> # a script tag
3907  |
3908  <noscript[^>]*>.*?</noscript> # a noscript tag
3909  |
3910  <template[^>]*>.*?</template> # a template tag
3911  )
3912  |
3913  </?(?:' . $tags . ')+ # opening tag (\'<tag\') or closing tag (\'</tag\')
3914  (?:
3915  (?:
3916  (?:
3917  \\s+\\w[\\w-]* # EITHER spaces, followed by attribute names
3918  (?:
3919  \\s*=?\\s* # equals
3920  (?>
3921  ".*?" # attribute values in double-quotes
3922  |
3923  \'.*?\' # attribute values in single-quotes
3924  |
3925  [^\'">\\s]+ # plain attribute values
3926  )
3927  )?
3928  )
3929  | # OR a single dash (for TYPO3 link tag)
3930  (?:
3931  \\s+-
3932  )
3933  )+\\s*
3934  | # OR only spaces
3935  \\s*
3936  )
3937  /?> # closing the tag with \'>\' or \'/>\'
3938  )';
3939  $splittedContent = preg_split('%' . $tagsRegEx . '%xs', $content, -1, PREG_SPLIT_DELIM_CAPTURE);
3940  // Reverse array if we are cropping from right.
3941  if ($chars < 0) {
3942  $splittedContent = array_reverse($splittedContent);
3943  }
3944  // Crop the text (chars of tag-blocks are not counted).
3945  $strLen = 0;
3946  // This is the offset of the content item which was cropped.
3947  $croppedOffset = null;
3948  $countSplittedContent = count($splittedContent);
3949  for ($offset = 0; $offset < $countSplittedContent; $offset++) {
3950  if ($offset % 2 === 0) {
3951  $tempContent = $splittedContent[$offset];
3952  $thisStrLen = mb_strlen(html_entity_decode($tempContent, ENT_COMPAT, 'UTF-8'), 'utf-8');
3953  if ($strLen + $thisStrLen > $absChars) {
3954  $croppedOffset = $offset;
3955  $cropPosition = $absChars - $strLen;
3956  // The snippet "&[^&\s;]{2,8};" in the RegEx below represents entities.
3957  $patternMatchEntityAsSingleChar = '(&[^&\\s;]{2,8};|.)';
3958  $cropRegEx = $chars < 0 ? '#' . $patternMatchEntityAsSingleChar . '{0,' . ($cropPosition + 1) . '}$#uis' : '#^' . $patternMatchEntityAsSingleChar . '{0,' . ($cropPosition + 1) . '}#uis';
3959  if (preg_match($cropRegEx, $tempContent, $croppedMatch)) {
3960  $tempContentPlusOneCharacter = $croppedMatch[0];
3961  } else {
3962  $tempContentPlusOneCharacter = false;
3963  }
3964  $cropRegEx = $chars < 0 ? '#' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}$#uis' : '#^' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}#uis';
3965  if (preg_match($cropRegEx, $tempContent, $croppedMatch)) {
3966  $tempContent = $croppedMatch[0];
3967  if ($crop2space && $tempContentPlusOneCharacter !== false) {
3968  $cropRegEx = $chars < 0 ? '#(?<=\\s)' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}$#uis' : '#^' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}(?=\\s)#uis';
3969  if (preg_match($cropRegEx, $tempContentPlusOneCharacter, $croppedMatch)) {
3970  $tempContent = $croppedMatch[0];
3971  }
3972  }
3973  }
3974  $splittedContent[$offset] = $tempContent;
3975  break;
3976  } else {
3977  $strLen += $thisStrLen;
3978  }
3979  }
3980  }
3981  // Close cropped tags.
3982  $closingTags = [];
3983  if ($croppedOffset !== null) {
3984  $openingTagRegEx = '#^<(\\w+)(?:\\s|>)#';
3985  $closingTagRegEx = '#^</(\\w+)(?:\\s|>)#';
3986  for ($offset = $croppedOffset - 1; $offset >= 0; $offset = $offset - 2) {
3987  if (substr($splittedContent[$offset], -2) === '/>') {
3988  // Ignore empty element tags (e.g. <br />).
3989  continue;
3990  }
3991  preg_match($chars < 0 ? $closingTagRegEx : $openingTagRegEx, $splittedContent[$offset], $matches);
3992  $tagName = isset($matches[1]) ? $matches[1] : null;
3993  if ($tagName !== null) {
3994  // Seek for the closing (or opening) tag.
3995  $countSplittedContent = count($splittedContent);
3996  for ($seekingOffset = $offset + 2; $seekingOffset < $countSplittedContent; $seekingOffset = $seekingOffset + 2) {
3997  preg_match($chars < 0 ? $openingTagRegEx : $closingTagRegEx, $splittedContent[$seekingOffset], $matches);
3998  $seekingTagName = isset($matches[1]) ? $matches[1] : null;
3999  if ($tagName === $seekingTagName) {
4000  // We found a matching tag.
4001  // Add closing tag only if it occurs after the cropped content item.
4002  if ($seekingOffset > $croppedOffset) {
4003  $closingTags[] = $splittedContent[$seekingOffset];
4004  }
4005  break;
4006  }
4007  }
4008  }
4009  }
4010  // Drop the cropped items of the content array. The $closingTags will be added later on again.
4011  array_splice($splittedContent, $croppedOffset + 1);
4012  }
4013  $splittedContent = array_merge($splittedContent, [
4014  $croppedOffset !== null ? $replacementForEllipsis : ''
4015  ], $closingTags);
4016  // Reverse array once again if we are cropping from the end.
4017  if ($chars < 0) {
4018  $splittedContent = array_reverse($splittedContent);
4019  }
4020  return implode('', $splittedContent);
4021  }
4022 
4032  public function removeBadHTML($text)
4033  {
4035  // Copyright 2002-2003 Thomas Bley
4036  $text = preg_replace([
4037  '\'<script[^>]*?>.*?</script[^>]*?>\'si',
4038  '\'<applet[^>]*?>.*?</applet[^>]*?>\'si',
4039  '\'<object[^>]*?>.*?</object[^>]*?>\'si',
4040  '\'<iframe[^>]*?>.*?</iframe[^>]*?>\'si',
4041  '\'<frameset[^>]*?>.*?</frameset[^>]*?>\'si',
4042  '\'<style[^>]*?>.*?</style[^>]*?>\'si',
4043  '\'<marquee[^>]*?>.*?</marquee[^>]*?>\'si',
4044  '\'<script[^>]*?>\'si',
4045  '\'<meta[^>]*?>\'si',
4046  '\'<base[^>]*?>\'si',
4047  '\'<applet[^>]*?>\'si',
4048  '\'<object[^>]*?>\'si',
4049  '\'<link[^>]*?>\'si',
4050  '\'<iframe[^>]*?>\'si',
4051  '\'<frame[^>]*?>\'si',
4052  '\'<frameset[^>]*?>\'si',
4053  '\'<input[^>]*?>\'si',
4054  '\'<form[^>]*?>\'si',
4055  '\'<embed[^>]*?>\'si',
4056  '\'background-image:url\'si',
4057  '\'<\\w+.*?(onabort|onbeforeunload|onblur|onchange|onclick|ondblclick|ondragdrop|onerror|onfilterchange|onfocus|onhelp|onkeydown|onkeypress|onkeyup|onload|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|onmove|onreadystatechange|onreset|onresize|onscroll|onselect|onselectstart|onsubmit|onunload).*?>\'si'
4058  ], '', $text);
4059  $text = preg_replace('/<a[^>]*href[[:space:]]*=[[:space:]]*["\']?[[:space:]]*javascript[^>]*/i', '', $text);
4060  // Return clean content
4061  return $text;
4062  }
4063 
4072  public function addParams($content, $conf)
4073  {
4074  // For XHTML compliance.
4075  $lowerCaseAttributes = true;
4076  if (!is_array($conf)) {
4077  return $content;
4078  }
4079  $key = 1;
4080  $parts = explode('<', $content);
4081  if ((int)$conf['_offset']) {
4082  $key = (int)$conf['_offset'] < 0 ? count($parts) + (int)$conf['_offset'] : (int)$conf['_offset'];
4083  }
4084  $subparts = explode('>', $parts[$key]);
4085  if (trim($subparts[0])) {
4086  // Get attributes and name
4087  $attribs = GeneralUtility::get_tag_attributes('<' . $subparts[0] . '>');
4088  list($tagName) = explode(' ', $subparts[0], 2);
4089  // adds/overrides attributes
4090  foreach ($conf as $pkey => $val) {
4091  if (substr($pkey, -1) !== '.' && $pkey[0] !== '_') {
4092  $tmpVal = isset($conf[$pkey . '.']) ? $this->stdWrap($conf[$pkey], $conf[$pkey . '.']) : (string)$val;
4093  if ($lowerCaseAttributes) {
4094  $pkey = strtolower($pkey);
4095  }
4096  if ($tmpVal !== '') {
4097  $attribs[$pkey] = $tmpVal;
4098  }
4099  }
4100  }
4101  // Re-assembles the tag and content
4102  $subparts[0] = trim($tagName . ' ' . GeneralUtility::implodeAttributes($attribs));
4103  $parts[$key] = implode('>', $subparts);
4104  $content = implode('<', $parts);
4105  }
4106  return $content;
4107  }
4108 
4119  public function filelink($theValue, $conf)
4120  {
4121  $conf['path'] = isset($conf['path.']) ? $this->stdWrap($conf['path'], $conf['path.']) : $conf['path'];
4122  $theFile = trim($conf['path']) . $theValue;
4123  if (!@is_file($theFile)) {
4124  return '';
4125  }
4126  $theFileEnc = str_replace('%2F', '/', rawurlencode($theFile));
4127  $title = $conf['title'];
4128  if (isset($conf['title.'])) {
4129  $title = $this->stdWrap($title, $conf['title.']);
4130  }
4131  $target = $conf['target'];
4132  if (isset($conf['target.'])) {
4133  $target = $this->stdWrap($target, $conf['target.']);
4134  }
4135  $tsfe = $this->getTypoScriptFrontendController();
4136 
4137  $typoLinkConf = [
4138  'parameter' => $theFileEnc,
4139  'fileTarget' => $target,
4140  'title' => $title,
4141  'ATagParams' => $this->getATagParams($conf)
4142  ];
4143 
4144  if (isset($conf['typolinkConfiguration.'])) {
4145  $additionalTypoLinkConfiguration = $conf['typolinkConfiguration.'];
4146  // We only allow additional configuration. This is why the generated conf overwrites the additional conf.
4147  ArrayUtility::mergeRecursiveWithOverrule($additionalTypoLinkConfiguration, $typoLinkConf);
4148  $typoLinkConf = $additionalTypoLinkConfiguration;
4149  }
4150 
4151  $theLinkWrap = $this->typoLink('|', $typoLinkConf);
4152  $theSize = filesize($theFile);
4153  $fI = GeneralUtility::split_fileref($theFile);
4154  $icon = '';
4155  if ($conf['icon']) {
4156  $conf['icon.']['path'] = isset($conf['icon.']['path.'])
4157  ? $this->stdWrap($conf['icon.']['path'], $conf['icon.']['path.'])
4158  : $conf['icon.']['path'];
4159  $iconP = !empty($conf['icon.']['path'])
4160  ? $conf['icon.']['path']
4161  : ExtensionManagementUtility::siteRelPath('frontend') . 'Resources/Public/Icons/FileIcons/';
4162  $conf['icon.']['ext'] = isset($conf['icon.']['ext.'])
4163  ? $this->stdWrap($conf['icon.']['ext'], $conf['icon.']['ext.'])
4164  : $conf['icon.']['ext'];
4165  $iconExt = !empty($conf['icon.']['ext']) ? '.' . $conf['icon.']['ext'] : '.gif';
4166  $icon = @is_file(($iconP . $fI['fileext'] . $iconExt))
4167  ? $iconP . $fI['fileext'] . $iconExt
4168  : $iconP . 'default' . $iconExt;
4169  // Checking for images: If image, then return link to thumbnail.
4170  $IEList = isset($conf['icon_image_ext_list.']) ? $this->stdWrap($conf['icon_image_ext_list'], $conf['icon_image_ext_list.']) : $conf['icon_image_ext_list'];
4171  $image_ext_list = str_replace(' ', '', strtolower($IEList));
4172  if ($fI['fileext'] && GeneralUtility::inList($image_ext_list, $fI['fileext'])) {
4173  if ($conf['iconCObject']) {
4174  $icon = $this->cObjGetSingle($conf['iconCObject'], $conf['iconCObject.'], 'iconCObject');
4175  } else {
4176  $notFoundThumb = ExtensionManagementUtility::siteRelPath('core') . 'Resources/Public/Images/NotFound.gif';
4177  $sizeParts = [64, 64];
4178  if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['thumbnails']) {
4179  // using the File Abstraction Layer to generate a preview image
4180  try {
4182  $fileObject = ResourceFactory::getInstance()->retrieveFileOrFolderObject($theFile);
4183  if ($fileObject->isMissing()) {
4184  $icon = $notFoundThumb;
4185  } else {
4186  $fileExtension = $fileObject->getExtension();
4187  if ($fileExtension === 'ttf' || GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], $fileExtension)) {
4188  if ($conf['icon_thumbSize'] || $conf['icon_thumbSize.']) {
4189  $thumbSize = isset($conf['icon_thumbSize.']) ? $this->stdWrap($conf['icon_thumbSize'], $conf['icon_thumbSize.']) : $conf['icon_thumbSize'];
4190  $sizeParts = explode('x', $thumbSize);
4191  }
4192  $icon = $fileObject->process(ProcessedFile::CONTEXT_IMAGEPREVIEW, [
4193  'width' => $sizeParts[0],
4194  'height' => $sizeParts[1]
4195  ])->getPublicUrl(true);
4196  }
4197  }
4198  } catch (ResourceDoesNotExistException $exception) {
4199  $icon = $notFoundThumb;
4200  }
4201  } else {
4202  $icon = $notFoundThumb;
4203  }
4204  $urlPrefix = '';
4205  if (parse_url($icon, PHP_URL_HOST) === null) {
4206  $urlPrefix = $tsfe->absRefPrefix;
4207  }
4208  $icon = '<img src="' . htmlspecialchars($urlPrefix . $icon) . '"' .
4209  ' width="' . (int)$sizeParts[0] . '" height="' . (int)$sizeParts[1] . '" ' .
4210  $this->getBorderAttr(' border="0"') . '' . $this->getAltParam($conf) . ' />';
4211  }
4212  } else {
4213  $conf['icon.']['widthAttribute'] = isset($conf['icon.']['widthAttribute.'])
4214  ? $this->stdWrap($conf['icon.']['widthAttribute'], $conf['icon.']['widthAttribute.'])
4215  : $conf['icon.']['widthAttribute'];
4216  $iconWidth = !empty($conf['icon.']['widthAttribute']) ? $conf['icon.']['widthAttribute'] : 18;
4217  $conf['icon.']['heightAttribute'] = isset($conf['icon.']['heightAttribute.'])
4218  ? $this->stdWrap($conf['icon.']['heightAttribute'], $conf['icon.']['heightAttribute.'])
4219  : $conf['icon.']['heightAttribute'];
4220  $iconHeight = !empty($conf['icon.']['heightAttribute']) ? (int)$conf['icon.']['heightAttribute'] : 16;
4221  $icon = '<img src="' . htmlspecialchars($tsfe->absRefPrefix . $icon) . '" width="' . (int)$iconWidth . '" height="' . (int)$iconHeight . '"'
4222  . $this->getBorderAttr(' border="0"') . $this->getAltParam($conf) . ' />';
4223  }
4224  if ($conf['icon_link'] && !$conf['combinedLink']) {
4225  $icon = $this->wrap($icon, $theLinkWrap);
4226  }
4227  $icon = isset($conf['icon.']) ? $this->stdWrap($icon, $conf['icon.']) : $icon;
4228  }
4229  $size = '';
4230  if ($conf['size']) {
4231  $size = isset($conf['size.']) ? $this->stdWrap($theSize, $conf['size.']) : $theSize;
4232  }
4233  // Wrapping file label
4234  if ($conf['removePrependedNumbers']) {
4235  $theValue = preg_replace('/_[0-9][0-9](\\.[[:alnum:]]*)$/', '\\1', $theValue);
4236  }
4237  if (isset($conf['labelStdWrap.'])) {
4238  $theValue = $this->stdWrap($theValue, $conf['labelStdWrap.']);
4239  }
4240  // Wrapping file
4241  $wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
4242  if ($conf['combinedLink']) {
4243  $theValue = $icon . $theValue;
4244  if ($conf['ATagBeforeWrap']) {
4245  $theValue = $this->wrap($this->wrap($theValue, $wrap), $theLinkWrap);
4246  } else {
4247  $theValue = $this->wrap($this->wrap($theValue, $theLinkWrap), $wrap);
4248  }
4249  $file = isset($conf['file.']) ? $this->stdWrap($theValue, $conf['file.']) : $theValue;
4250  // output
4251  $output = $file . $size;
4252  } else {
4253  if ($conf['ATagBeforeWrap']) {
4254  $theValue = $this->wrap($this->wrap($theValue, $wrap), $theLinkWrap);
4255  } else {
4256  $theValue = $this->wrap($this->wrap($theValue, $theLinkWrap), $wrap);
4257  }
4258  $file = isset($conf['file.']) ? $this->stdWrap($theValue, $conf['file.']) : $theValue;
4259  // output
4260  $output = $icon . $file . $size;
4261  }
4262  if (isset($conf['stdWrap.'])) {
4263  $output = $this->stdWrap($output, $conf['stdWrap.']);
4264  }
4265  return $output;
4266  }
4267 
4275  public function calc($val)
4276  {
4277  $parts = GeneralUtility::splitCalc($val, '+-*/');
4278  $value = 0;
4279  foreach ($parts as $part) {
4280  $theVal = $part[1];
4281  $sign = $part[0];
4282  if ((string)(int)$theVal === (string)$theVal) {
4283  $theVal = (int)$theVal;
4284  } else {
4285  $theVal = 0;
4286  }
4287  if ($sign === '-') {
4288  $value -= $theVal;
4289  }
4290  if ($sign === '+') {
4291  $value += $theVal;
4292  }
4293  if ($sign === '/') {
4294  if ((int)$theVal) {
4295  $value /= (int)$theVal;
4296  }
4297  }
4298  if ($sign === '*') {
4299  $value *= $theVal;
4300  }
4301  }
4302  return $value;
4303  }
4304 
4314  public function calcIntExplode($delim, $string)
4315  {
4316  $temp = explode($delim, $string);
4317  foreach ($temp as $key => $val) {
4318  $temp[$key] = (int)$this->calc($val);
4319  }
4320  return $temp;
4321  }
4322 
4334  public function splitObj($value, $conf)
4335  {
4336  $conf['token'] = isset($conf['token.']) ? $this->stdWrap($conf['token'], $conf['token.']) : $conf['token'];
4337  if ($conf['token'] === '') {
4338  return $value;
4339  }
4340  $valArr = explode($conf['token'], $value);
4341 
4342  // return value directly by returnKey. No further processing
4343  if (!empty($valArr) && (MathUtility::canBeInterpretedAsInteger($conf['returnKey']) || $conf['returnKey.'])) {
4344  $key = isset($conf['returnKey.']) ? (int)$this->stdWrap($conf['returnKey'], $conf['returnKey.']) : (int)$conf['returnKey'];
4345  return isset($valArr[$key]) ? $valArr[$key] : '';
4346  }
4347 
4348  // return the amount of elements. No further processing
4349  if (!empty($valArr) && ($conf['returnCount'] || $conf['returnCount.'])) {
4350  $returnCount = isset($conf['returnCount.']) ? (bool)$this->stdWrap($conf['returnCount'], $conf['returnCount.']) : (bool)$conf['returnCount'];
4351  return $returnCount ? count($valArr) : 0;
4352  }
4353 
4354  // calculate splitCount
4355  $splitCount = count($valArr);
4356  $max = isset($conf['max.']) ? (int)$this->stdWrap($conf['max'], $conf['max.']) : (int)$conf['max'];
4357  if ($max && $splitCount > $max) {
4358  $splitCount = $max;
4359  }
4360  $min = isset($conf['min.']) ? (int)$this->stdWrap($conf['min'], $conf['min.']) : (int)$conf['min'];
4361  if ($min && $splitCount < $min) {
4362  $splitCount = $min;
4363  }
4364  $wrap = isset($conf['wrap.']) ? (string)$this->stdWrap($conf['wrap'], $conf['wrap.']) : (string)$conf['wrap'];
4365  $cObjNumSplitConf = isset($conf['cObjNum.']) ? (string)$this->stdWrap($conf['cObjNum'], $conf['cObjNum.']) : (string)$conf['cObjNum'];
4366  $splitArr = [];
4367  if ($wrap !== '' || $cObjNumSplitConf !== '') {
4368  $splitArr['wrap'] = $wrap;
4369  $splitArr['cObjNum'] = $cObjNumSplitConf;
4370  $splitArr = $GLOBALS['TSFE']->tmpl->splitConfArray($splitArr, $splitCount);
4371  }
4372  $content = '';
4373  for ($a = 0; $a < $splitCount; $a++) {
4374  $this->getTypoScriptFrontendController()->register['SPLIT_COUNT'] = $a;
4375  $value = '' . $valArr[$a];
4376  $this->data[$this->currentValKey] = $value;
4377  if ($splitArr[$a]['cObjNum']) {
4378  $objName = (int)$splitArr[$a]['cObjNum'];
4379  $value = isset($conf[$objName . '.'])
4380  ? $this->stdWrap($this->cObjGet($conf[$objName . '.'], $objName . '.'), $conf[$objName . '.'])
4381  : $this->cObjGet($conf[$objName . '.'], $objName . '.');
4382  }
4383  $wrap = isset($splitArr[$a]['wrap.']) ? $this->stdWrap($splitArr[$a]['wrap'], $splitArr[$a]['wrap.']) : $splitArr[$a]['wrap'];
4384  if ($wrap) {
4385  $value = $this->wrap($value, $wrap);
4386  }
4387  $content .= $value;
4388  }
4389  return $content;
4390  }
4391 
4399  protected function replacement($content, array $configuration)
4400  {
4401  // Sorts actions in configuration by numeric index
4402  ksort($configuration, SORT_NUMERIC);
4403  foreach ($configuration as $index => $action) {
4404  // Checks whether we have an valid action and a numeric key ending with a dot ("10.")
4405  if (is_array($action) && substr($index, -1) === '.' && MathUtility::canBeInterpretedAsInteger(substr($index, 0, -1))) {
4406  $content = $this->replacementSingle($content, $action);
4407  }
4408  }
4409  return $content;
4410  }
4411 
4419  protected function replacementSingle($content, array $configuration)
4420  {
4421  if ((isset($configuration['search']) || isset($configuration['search.'])) && (isset($configuration['replace']) || isset($configuration['replace.']))) {
4422  // Gets the strings
4423  $search = isset($configuration['search.']) ? $this->stdWrap($configuration['search'], $configuration['search.']) : $configuration['search'];
4424  $replace = isset($configuration['replace.']) ? $this->stdWrap($configuration['replace'], $configuration['replace.']) : $configuration['replace'];
4425  // Determines whether regular expression shall be used
4426  if (isset($configuration['useRegExp']) || $configuration['useRegExp.']) {
4427  $useRegularExpression = isset($configuration['useRegExp.']) ? $this->stdWrap($configuration['useRegExp'], $configuration['useRegExp.']) : $configuration['useRegExp'];
4428  }
4429  // Determines whether replace-pattern uses option-split
4430  if (isset($configuration['useOptionSplitReplace']) || isset($configuration['useOptionSplitReplace.'])) {
4431  $useOptionSplitReplace = isset($configuration['useOptionSplitReplace.']) ? $this->stdWrap($configuration['useOptionSplitReplace'], $configuration['useOptionSplitReplace.']) : $configuration['useOptionSplitReplace'];
4432  }
4433 
4434  // Performs a replacement by preg_replace()
4435  if (isset($useRegularExpression)) {
4436  // Get separator-character which precedes the string and separates search-string from the modifiers
4437  $separator = $search[0];
4438  $startModifiers = strrpos($search, $separator);
4439  if ($separator !== false && $startModifiers > 0) {
4440  $modifiers = substr($search, $startModifiers + 1);
4441  // remove "e" (eval-modifier), which would otherwise allow to run arbitrary PHP-code
4442  $modifiers = str_replace('e', '', $modifiers);
4443  $search = substr($search, 0, ($startModifiers + 1)) . $modifiers;
4444  }
4445  if (empty($useOptionSplitReplace)) {
4446  $content = preg_replace($search, $replace, $content);
4447  } else {
4448  // init for replacement
4449  $splitCount = preg_match_all($search, $content, $matches);
4450  $replaceArray = $this->getTypoScriptFrontendController()->tmpl->splitConfArray([$replace], $splitCount);
4451  $replaceCount = 0;
4452 
4453  $replaceCallback = function ($match) use ($replaceArray, $search, &$replaceCount) {
4454  $replaceCount++;
4455  return preg_replace($search, $replaceArray[$replaceCount - 1][0], $match[0]);
4456  };
4457  $content = preg_replace_callback($search, $replaceCallback, $content);
4458  }
4459  } else {
4460  if (empty($useOptionSplitReplace)) {
4461  $content = str_replace($search, $replace, $content);
4462  } else {
4463  // turn search-string into a preg-pattern
4464  $searchPreg = '#' . preg_quote($search, '#') . '#';
4465 
4466  // init for replacement
4467  $splitCount = preg_match_all($searchPreg, $content, $matches);
4468  $replaceArray = $this->getTypoScriptFrontendController()->tmpl->splitConfArray([$replace], $splitCount);
4469  $replaceCount = 0;
4470 
4471  $replaceCallback = function () use ($replaceArray, $search, &$replaceCount) {
4472  $replaceCount++;
4473  return $replaceArray[$replaceCount - 1][0];
4474  };
4475  $content = preg_replace_callback($searchPreg, $replaceCallback, $content);
4476  }
4477  }
4478  }
4479  return $content;
4480  }
4481 
4490  protected function round($content, array $conf = [])
4491  {
4492  $decimals = isset($conf['decimals.']) ? $this->stdWrap($conf['decimals'], $conf['decimals.']) : $conf['decimals'];
4493  $type = isset($conf['roundType.']) ? $this->stdWrap($conf['roundType'], $conf['roundType.']) : $conf['roundType'];
4494  $floatVal = (float)$content;
4495  switch ($type) {
4496  case 'ceil':
4497  $content = ceil($floatVal);
4498  break;
4499  case 'floor':
4500  $content = floor($floatVal);
4501  break;
4502  case 'round':
4503 
4504  default:
4505  $content = round($floatVal, (int)$decimals);
4506  }
4507  return $content;
4508  }
4509 
4518  public function numberFormat($content, $conf)
4519  {
4520  $decimals = isset($conf['decimals.']) ? (int)$this->stdWrap($conf['decimals'], $conf['decimals.']) : (int)$conf['decimals'];
4521  $dec_point = isset($conf['dec_point.']) ? $this->stdWrap($conf['dec_point'], $conf['dec_point.']) : $conf['dec_point'];
4522  $thousands_sep = isset($conf['thousands_sep.']) ? $this->stdWrap($conf['thousands_sep'], $conf['thousands_sep.']) : $conf['thousands_sep'];
4523  return number_format((float)$content, $decimals, $dec_point, $thousands_sep);
4524  }
4525 
4545  public function parseFunc($theValue, $conf, $ref = '')
4546  {
4547  // Fetch / merge reference, if any
4548  if ($ref) {
4549  $temp_conf = [
4550  'parseFunc' => $ref,
4551  'parseFunc.' => $conf
4552  ];
4553  $temp_conf = $this->mergeTSRef($temp_conf, 'parseFunc');
4554  $conf = $temp_conf['parseFunc.'];
4555  }
4556  // Process:
4557  if ((string)$conf['externalBlocks'] === '') {
4558  return $this->_parseFunc($theValue, $conf);
4559  }
4560  $tags = strtolower(implode(',', GeneralUtility::trimExplode(',', $conf['externalBlocks'])));
4561  $htmlParser = GeneralUtility::makeInstance(HtmlParser::class);
4562  $parts = $htmlParser->splitIntoBlock($tags, $theValue);
4563  foreach ($parts as $k => $v) {
4564  if ($k % 2) {
4565  // font:
4566  $tagName = strtolower($htmlParser->getFirstTagName($v));
4567  $cfg = $conf['externalBlocks.'][$tagName . '.'];
4568  if ($cfg['stripNLprev'] || $cfg['stripNL']) {
4569  $parts[$k - 1] = preg_replace('/' . CR . '?' . LF . '[ ]*$/', '', $parts[$k - 1]);
4570  }
4571  if ($cfg['stripNLnext'] || $cfg['stripNL']) {
4572  $parts[$k + 1] = preg_replace('/^[ ]*' . CR . '?' . LF . '/', '', $parts[$k + 1]);
4573  }
4574  }
4575  }
4576  foreach ($parts as $k => $v) {
4577  if ($k % 2) {
4578  $tag = $htmlParser->getFirstTag($v);
4579  $tagName = strtolower($htmlParser->getFirstTagName($v));
4580  $cfg = $conf['externalBlocks.'][$tagName . '.'];
4581  if ($cfg['callRecursive']) {
4582  $parts[$k] = $this->parseFunc($htmlParser->removeFirstAndLastTag($v), $conf);
4583  if (!$cfg['callRecursive.']['dontWrapSelf']) {
4584  if ($cfg['callRecursive.']['alternativeWrap']) {
4585  $parts[$k] = $this->wrap($parts[$k], $cfg['callRecursive.']['alternativeWrap']);
4586  } else {
4587  if (is_array($cfg['callRecursive.']['tagStdWrap.'])) {
4588  $tag = $this->stdWrap($tag, $cfg['callRecursive.']['tagStdWrap.']);
4589  }
4590  $parts[$k] = $tag . $parts[$k] . '</' . $tagName . '>';
4591  }
4592  }
4593  } elseif ($cfg['HTMLtableCells']) {
4594  $rowParts = $htmlParser->splitIntoBlock('tr', $parts[$k]);
4595  foreach ($rowParts as $kk => $vv) {
4596  if ($kk % 2) {
4597  $colParts = $htmlParser->splitIntoBlock('td,th', $vv);
4598  $cc = 0;
4599  foreach ($colParts as $kkk => $vvv) {
4600  if ($kkk % 2) {
4601  $cc++;
4602  $tag = $htmlParser->getFirstTag($vvv);
4603  $tagName = strtolower($htmlParser->getFirstTagName($vvv));
4604  $colParts[$kkk] = $htmlParser->removeFirstAndLastTag($vvv);
4605  if ($cfg['HTMLtableCells.'][$cc . '.']['callRecursive'] || !isset($cfg['HTMLtableCells.'][$cc . '.']['callRecursive']) && $cfg['HTMLtableCells.']['default.']['callRecursive']) {
4606  if ($cfg['HTMLtableCells.']['addChr10BetweenParagraphs']) {
4607  $colParts[$kkk] = str_replace('</p><p>', '</p>' . LF . '<p>', $colParts[$kkk]);
4608  }
4609  $colParts[$kkk] = $this->parseFunc($colParts[$kkk], $conf);
4610  }
4611  $tagStdWrap = is_array($cfg['HTMLtableCells.'][$cc . '.']['tagStdWrap.'])
4612  ? $cfg['HTMLtableCells.'][$cc . '.']['tagStdWrap.']
4613  : $cfg['HTMLtableCells.']['default.']['tagStdWrap.'];
4614  if (is_array($tagStdWrap)) {
4615  $tag = $this->stdWrap($tag, $tagStdWrap);
4616  }
4617  $stdWrap = is_array($cfg['HTMLtableCells.'][$cc . '.']['stdWrap.'])
4618  ? $cfg['HTMLtableCells.'][$cc . '.']['stdWrap.']
4619  : $cfg['HTMLtableCells.']['default.']['stdWrap.'];
4620  if (is_array($stdWrap)) {
4621  $colParts[$kkk] = $this->stdWrap($colParts[$kkk], $stdWrap);
4622  }
4623  $colParts[$kkk] = $tag . $colParts[$kkk] . '</' . $tagName . '>';
4624  }
4625  }
4626  $rowParts[$kk] = implode('', $colParts);
4627  }
4628  }
4629  $parts[$k] = implode('', $rowParts);
4630  }
4631  if (is_array($cfg['stdWrap.'])) {
4632  $parts[$k] = $this->stdWrap($parts[$k], $cfg['stdWrap.']);
4633  }
4634  } else {
4635  $parts[$k] = $this->_parseFunc($parts[$k], $conf);
4636  }
4637  }
4638  return implode('', $parts);
4639  }
4640 
4650  public function _parseFunc($theValue, $conf)
4651  {
4652  if (!empty($conf['if.']) && !$this->checkIf($conf['if.'])) {
4653  return $theValue;
4654  }
4655  // Indicates that the data is from within a tag.
4656  $inside = false;
4657  // Pointer to the total string position
4658  $pointer = 0;
4659  // Loaded with the current typo-tag if any.
4660  $currentTag = '';
4661  $stripNL = 0;
4662  $contentAccum = [];
4663  $contentAccumP = 0;
4664  $allowTags = strtolower(str_replace(' ', '', $conf['allowTags']));
4665  $denyTags = strtolower(str_replace(' ', '', $conf['denyTags']));
4666  $totalLen = strlen($theValue);
4667  do {
4668  if (!$inside) {
4669  if (!is_array($currentTag)) {
4670  // These operations should only be performed on code outside the typotags...
4671  // data: this checks that we enter tags ONLY if the first char in the tag is alphanumeric OR '/'
4672  $len_p = 0;
4673  $c = 100;
4674  do {
4675  $len = strcspn(substr($theValue, $pointer + $len_p), '<');
4676  $len_p += $len + 1;
4677  $endChar = ord(strtolower(substr($theValue, $pointer + $len_p, 1)));
4678  $c--;
4679  } while ($c > 0 && $endChar && ($endChar < 97 || $endChar > 122) && $endChar != 47);
4680  $len = $len_p - 1;
4681  } else {
4682  // If we're inside a currentTag, just take it to the end of that tag!
4683  $tempContent = strtolower(substr($theValue, $pointer));
4684  $len = strpos($tempContent, '</' . $currentTag[0]);
4685  if (is_string($len) && !$len) {
4686  $len = strlen($tempContent);
4687  }
4688  }
4689  // $data is the content until the next <tag-start or end is detected.
4690  // In case of a currentTag set, this would mean all data between the start- and end-tags
4691  $data = substr($theValue, $pointer, $len);
4692  if ($data != '') {
4693  if ($stripNL) {
4694  // If the previous tag was set to strip NewLines in the beginning of the next data-chunk.
4695  $data = preg_replace('/^[ ]*' . CR . '?' . LF . '/', '', $data);
4696  }
4697  // These operations should only be performed on code outside the tags...
4698  if (!is_array($currentTag)) {
4699  // Constants
4700  $tsfe = $this->getTypoScriptFrontendController();
4701  $tmpConstants = $tsfe->tmpl->setup['constants.'];
4702  if ($conf['constants'] && is_array($tmpConstants)) {
4703  foreach ($tmpConstants as $key => $val) {
4704  if (is_string($val)) {
4705  $data = str_replace('###' . $key . '###', $val, $data);
4706  }
4707  }
4708  }
4709  // Short
4710  if (is_array($conf['short.'])) {
4711  $shortWords = $conf['short.'];
4712  krsort($shortWords);
4713  foreach ($shortWords as $key => $val) {
4714  if (is_string($val)) {
4715  $data = str_replace($key, $val, $data);
4716  }
4717  }
4718  }
4719  // stdWrap
4720  if (is_array($conf['plainTextStdWrap.'])) {
4721  $data = $this->stdWrap($data, $conf['plainTextStdWrap.']);
4722  }
4723  // userFunc
4724  if ($conf['userFunc']) {
4725  $data = $this->callUserFunction($conf['userFunc'], $conf['userFunc.'], $data);
4726  }
4727  // Makelinks: (Before search-words as we need the links to be generated when searchwords go on...!)
4728  if ($conf['makelinks']) {
4729  $data = $this->http_makelinks($data, $conf['makelinks.']['http.']);
4730  $data = $this->mailto_makelinks($data, $conf['makelinks.']['mailto.']);
4731  }
4732  // Search Words:
4733  if ($tsfe->no_cache && $conf['sword'] && is_array($tsfe->sWordList) && $tsfe->sWordRegEx) {
4734  $newstring = '';
4735  do {
4736  $pregSplitMode = 'i';
4737  if (isset($tsfe->config['config']['sword_noMixedCase']) && !empty($tsfe->config['config']['sword_noMixedCase'])) {
4738  $pregSplitMode = '';
4739  }
4740  $pieces = preg_split('/' . $tsfe->sWordRegEx . '/' . $pregSplitMode, $data, 2);
4741  $newstring .= $pieces[0];
4742  $match_len = strlen($data) - (strlen($pieces[0]) + strlen($pieces[1]));
4743  $inTag = false;
4744  if (strstr($pieces[0], '<') || strstr($pieces[0], '>')) {
4745  // Returns TRUE, if a '<' is closer to the string-end than '>'.
4746  // This is the case if we're INSIDE a tag (that could have been
4747  // made by makelinks...) and we must secure, that the inside of a tag is
4748  // not marked up.
4749  $inTag = strrpos($pieces[0], '<') > strrpos($pieces[0], '>');
4750  }
4751  // The searchword:
4752  $match = substr($data, strlen($pieces[0]), $match_len);
4753  if (trim($match) && strlen($match) > 1 && !$inTag) {
4754  $match = $this->wrap($match, $conf['sword']);
4755  }
4756  // Concatenate the Search Word again.
4757  $newstring .= $match;
4758  $data = $pieces[1];
4759  } while ($pieces[1]);
4760  $data = $newstring;
4761  }
4762  }
4763  $contentAccum[$contentAccumP] .= $data;
4764  }
4765  $inside = true;
4766  } else {
4767  // tags
4768  $len = strcspn(substr($theValue, $pointer), '>') + 1;
4769  $data = substr($theValue, $pointer, $len);
4770  if (StringUtility::endsWith($data, '/>') && strpos($data, '<link ') !== 0) {
4771  $tagContent = substr($data, 1, -2);
4772  } else {
4773  $tagContent = substr($data, 1, -1);
4774  }
4775  $tag = explode(' ', trim($tagContent), 2);
4776  $tag[0] = strtolower($tag[0]);
4777  if ($tag[0][0] === '/') {
4778  $tag[0] = substr($tag[0], 1);
4779  $tag['out'] = 1;
4780  }
4781  if ($conf['tags.'][$tag[0]]) {
4782  $treated = false;
4783  $stripNL = false;
4784  // in-tag
4785  if (!$currentTag && !$tag['out']) {
4786  // $currentTag (array!) is the tag we are currently processing
4787  $currentTag = $tag;
4788  $contentAccumP++;
4789  $treated = true;
4790  // in-out-tag: img and other empty tags
4791  if (preg_match('/^(area|base|br|col|hr|img|input|meta|param)$/i', $tag[0])) {
4792  $tag['out'] = 1;
4793  }
4794  }
4795  // out-tag
4796  if ($currentTag[0] === $tag[0] && $tag['out']) {
4797  $theName = $conf['tags.'][$tag[0]];
4798  $theConf = $conf['tags.'][$tag[0] . '.'];
4799  // This flag indicates, that NL- (13-10-chars) should be stripped first and last.
4800  $stripNL = (bool)$theConf['stripNL'];
4801  // This flag indicates, that this TypoTag section should NOT be included in the nonTypoTag content.
4802  $breakOut = (bool)$theConf['breakoutTypoTagContent'];
4803  $this->parameters = [];
4804  if ($currentTag[1]) {
4805  $params = GeneralUtility::get_tag_attributes($currentTag[1]);
4806  if (is_array($params)) {
4807  foreach ($params as $option => $val) {
4808  $this->parameters[strtolower($option)] = $val;
4809  }
4810  }
4811  }
4812  $this->parameters['allParams'] = trim($currentTag[1]);
4813  // Removes NL in the beginning and end of the tag-content AND at the end of the currentTagBuffer.
4814  // $stripNL depends on the configuration of the current tag
4815  if ($stripNL) {
4816  $contentAccum[$contentAccumP - 1] = preg_replace('/' . CR . '?' . LF . '[ ]*$/', '', $contentAccum[$contentAccumP - 1]);
4817  $contentAccum[$contentAccumP] = preg_replace('/^[ ]*' . CR . '?' . LF . '/', '', $contentAccum[$contentAccumP]);
4818  $contentAccum[$contentAccumP] = preg_replace('/' . CR . '?' . LF . '[ ]*$/', '', $contentAccum[$contentAccumP]);
4819  }
4820  $this->data[$this->currentValKey] = $contentAccum[$contentAccumP];
4821  $newInput = $this->cObjGetSingle($theName, $theConf, '/parseFunc/.tags.' . $tag[0]);
4822  // fetch the content object
4823  $contentAccum[$contentAccumP] = $newInput;
4824  $contentAccumP++;
4825  // If the TypoTag section
4826  if (!$breakOut) {
4827  $contentAccum[$contentAccumP - 2] .= $contentAccum[$contentAccumP - 1] . $contentAccum[$contentAccumP];
4828  unset($contentAccum[$contentAccumP]);
4829  unset($contentAccum[$contentAccumP - 1]);
4830  $contentAccumP -= 2;
4831  }
4832  unset($currentTag);
4833  $treated = true;
4834  }
4835  // other tags
4836  if (!$treated) {
4837  $contentAccum[$contentAccumP] .= $data;
4838  }
4839  } else {
4840  // If a tag was not a typo tag, then it is just added to the content
4841  $stripNL = false;
4842  if (GeneralUtility::inList($allowTags, $tag[0]) || $denyTags != '*' && !GeneralUtility::inList($denyTags, $tag[0])) {
4843  $contentAccum[$contentAccumP] .= $data;
4844  } else {
4845  $contentAccum[$contentAccumP] .= htmlspecialchars($data);
4846  }
4847  }
4848  $inside = false;
4849  }
4850  $pointer += $len;
4851  } while ($pointer < $totalLen);
4852  // Parsing nonTypoTag content (all even keys):
4853  reset($contentAccum);
4854  $contentAccumCount = count($contentAccum);
4855  for ($a = 0; $a < $contentAccumCount; $a++) {
4856  if ($a % 2 != 1) {
4857  // stdWrap
4858  if (is_array($conf['nonTypoTagStdWrap.'])) {
4859  $contentAccum[$a] = $this->stdWrap($contentAccum[$a], $conf['nonTypoTagStdWrap.']);
4860  }
4861  // userFunc
4862  if ($conf['nonTypoTagUserFunc']) {
4863  $contentAccum[$a] = $this->callUserFunction($conf['nonTypoTagUserFunc'], $conf['nonTypoTagUserFunc.'], $contentAccum[$a]);
4864  }
4865  }
4866  }
4867  return implode('', $contentAccum);
4868  }
4869 
4878  public function encaps_lineSplit($theValue, $conf)
4879  {
4880  if ((string)$theValue === '') {
4881  return '';
4882  }
4883  $lParts = explode(LF, $theValue);
4884 
4885  // When the last element is an empty linebreak we need to remove it, otherwise we will have a duplicate empty line.
4886  $lastPartIndex = count($lParts) - 1;
4887  if ($lParts[$lastPartIndex] === '' && trim($lParts[$lastPartIndex - 1], CR) === '') {
4888  array_pop($lParts);
4889  }
4890 
4891  $encapTags = GeneralUtility::trimExplode(',', strtolower($conf['encapsTagList']), true);
4892  $nonWrappedTag = $conf['nonWrappedTag'];
4893  $defaultAlign = isset($conf['defaultAlign.'])
4894  ? trim($this->stdWrap($conf['defaultAlign'], $conf['defaultAlign.']))
4895  : trim($conf['defaultAlign']);
4896 
4897  $str_content = '';
4898  foreach ($lParts as $k => $l) {
4899  $sameBeginEnd = 0;
4900  $emptyTag = false;
4901  $l = trim($l);
4902  $attrib = [];
4903  $nonWrapped = false;
4904  $tagName = '';
4905  if ($l[0] === '<' && substr($l, -1) === '>') {
4906  $fwParts = explode('>', substr($l, 1), 2);
4907  list($tagName) = explode(' ', $fwParts[0], 2);
4908  if (!$fwParts[1]) {
4909  if (substr($tagName, -1) === '/') {
4910  $tagName = substr($tagName, 0, -1);
4911  }
4912  if (substr($fwParts[0], -1) === '/') {
4913  $sameBeginEnd = 1;
4914  $emptyTag = true;
4915  $attrib = GeneralUtility::get_tag_attributes('<' . substr($fwParts[0], 0, -1) . '>');
4916  }
4917  } else {
4918  $backParts = GeneralUtility::revExplode('<', substr($fwParts[1], 0, -1), 2);
4919  $attrib = GeneralUtility::get_tag_attributes('<' . $fwParts[0] . '>');
4920  $str_content = $backParts[0];
4921  $sameBeginEnd = substr(strtolower($backParts[1]), 1, strlen($tagName)) === strtolower($tagName);
4922  }
4923  }
4924  if ($sameBeginEnd && in_array(strtolower($tagName), $encapTags)) {
4925  $uTagName = strtoupper($tagName);
4926  $uTagName = strtoupper($conf['remapTag.'][$uTagName] ? $conf['remapTag.'][$uTagName] : $uTagName);
4927  } else {
4928  $uTagName = strtoupper($nonWrappedTag);
4929  // The line will be wrapped: $uTagName should not be an empty tag
4930  $emptyTag = false;
4931  $str_content = $lParts[$k];
4932  $nonWrapped = true;
4933  $attrib = [];
4934  }
4935  // Wrapping all inner-content:
4936  if (is_array($conf['innerStdWrap_all.'])) {
4937  $str_content = $this->stdWrap($str_content, $conf['innerStdWrap_all.']);
4938  }
4939  if ($uTagName) {
4940  // Setting common attributes
4941  if (is_array($conf['addAttributes.'][$uTagName . '.'])) {
4942  foreach ($conf['addAttributes.'][$uTagName . '.'] as $kk => $vv) {
4943  if (!is_array($vv)) {
4944  if ((string)$conf['addAttributes.'][$uTagName . '.'][$kk . '.']['setOnly'] === 'blank') {
4945  if ((string)$attrib[$kk] === '') {
4946  $attrib[$kk] = $vv;
4947  }
4948  } elseif ((string)$conf['addAttributes.'][$uTagName . '.'][$kk . '.']['setOnly'] === 'exists') {
4949  if (!isset($attrib[$kk])) {
4950  $attrib[$kk] = $vv;
4951  }
4952  } else {
4953  $attrib[$kk] = $vv;
4954  }
4955  }
4956  }
4957  }
4958  // Wrapping all inner-content:
4959  if (is_array($conf['encapsLinesStdWrap.'][$uTagName . '.'])) {
4960  $str_content = $this->stdWrap($str_content, $conf['encapsLinesStdWrap.'][$uTagName . '.']);
4961  }
4962  // Default align
4963  if (!$attrib['align'] && $defaultAlign) {
4964  $attrib['align'] = $defaultAlign;
4965  }
4966  $params = GeneralUtility::implodeAttributes($attrib, 1);
4967  if (!($conf['removeWrapping'] && !($emptyTag && $conf['removeWrapping.']['keepSingleTag']))) {
4968  if ($emptyTag) {
4969  $str_content = '<' . strtolower($uTagName) . (trim($params) ? ' ' . trim($params) : '') . ' />';
4970  } else {
4971  $str_content = '<' . strtolower($uTagName) . (trim($params) ? ' ' . trim($params) : '') . '>' . $str_content . '</' . strtolower($uTagName) . '>';
4972  }
4973  }
4974  }
4975  if ($nonWrapped && $conf['wrapNonWrappedLines']) {
4976  $str_content = $this->wrap($str_content, $conf['wrapNonWrappedLines']);
4977  }
4978  $lParts[$k] = $str_content;
4979  }
4980  return implode(LF, $lParts);
4981  }
4982 
4993  public function http_makelinks($data, $conf)
4994  {
4995  $aTagParams = $this->getATagParams($conf);
4996  $textstr = '';
4997  foreach ([ 'http://', 'https://' ] as $scheme) {
4998  $textpieces = explode($scheme, $data);
4999  $pieces = count($textpieces);
5000  $textstr = $textpieces[0];
5001  for ($i = 1; $i < $pieces; $i++) {
5002  $len = strcspn($textpieces[$i], chr(32) . TAB . CRLF);
5003  if (trim(substr($textstr, -1)) === '' && $len) {
5004  $lastChar = substr($textpieces[$i], $len - 1, 1);
5005  if (!preg_match('/[A-Za-z0-9\\/#_-]/', $lastChar)) {
5006  $len--;
5007  }
5008  // Included '\/' 3/12
5009  $parts[0] = substr($textpieces[$i], 0, $len);
5010  $parts[1] = substr($textpieces[$i], $len);
5011  $keep = $conf['keep'];
5012  $linkParts = parse_url($scheme . $parts[0]);
5013  $linktxt = '';
5014  if (strstr($keep, 'scheme')) {
5015  $linktxt = $scheme;
5016  }
5017  $linktxt .= $linkParts['host'];
5018  if (strstr($keep, 'path')) {
5019  $linktxt .= $linkParts['path'];
5020  // Added $linkParts['query'] 3/12
5021  if (strstr($keep, 'query') && $linkParts['query']) {
5022  $linktxt .= '?' . $linkParts['query'];
5023  } elseif ($linkParts['path'] === '/') {
5024  $linktxt = substr($linktxt, 0, -1);
5025  }
5026  }
5027  if (isset($conf['extTarget'])) {
5028  if (isset($conf['extTarget.'])) {
5029  $target = $this->stdWrap($conf['extTarget'], $conf['extTarget.']);
5030  } else {
5031  $target = $conf['extTarget'];
5032  }
5033  } else {
5034  $target = $this->getTypoScriptFrontendController()->extTarget;
5035  }
5036 
5037  // check for jump URLs or similar
5038  $linkUrl = $this->processUrl(UrlProcessorInterface::CONTEXT_COMMON, $scheme . $parts[0], $conf);
5039 
5040  $res = '<a href="' . htmlspecialchars($linkUrl) . '"'
5041  . ($target !== '' ? ' target="' . htmlspecialchars($target) . '"' : '')
5042  . $aTagParams . $this->extLinkATagParams(('http://' . $parts[0]), 'url') . '>';
5043 
5044  $wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
5045  if ((string)$conf['ATagBeforeWrap'] !== '') {
5046  $res = $res . $this->wrap($linktxt, $wrap) . '</a>';
5047  } else {
5048  $res = $this->wrap($res . $linktxt . '</a>', $wrap);
5049  }
5050  $textstr .= $res . $parts[1];
5051  } else {
5052  $textstr .= $scheme . $textpieces[$i];
5053  }
5054  }
5055  $data = $textstr;
5056  }
5057  return $textstr;
5058  }
5059 
5069  public function mailto_makelinks($data, $conf)
5070  {
5071  // http-split
5072  $aTagParams = $this->getATagParams($conf);
5073  $textpieces = explode('mailto:', $data);
5074  $pieces = count($textpieces);
5075  $textstr = $textpieces[0];
5076  $tsfe = $this->getTypoScriptFrontendController();
5077  for ($i = 1; $i < $pieces; $i++) {
5078  $len = strcspn($textpieces[$i], chr(32) . TAB . CRLF);
5079  if (trim(substr($textstr, -1)) === '' && $len) {
5080  $lastChar = substr($textpieces[$i], $len - 1, 1);
5081  if (!preg_match('/[A-Za-z0-9]/', $lastChar)) {
5082  $len--;
5083  }
5084  $parts[0] = substr($textpieces[$i], 0, $len);
5085  $parts[1] = substr($textpieces[$i], $len);
5086  $linktxt = preg_replace('/\\?.*/', '', $parts[0]);
5087  list($mailToUrl, $linktxt) = $this->getMailTo($parts[0], $linktxt);
5088  $mailToUrl = $tsfe->spamProtectEmailAddresses === 'ascii' ? $mailToUrl : htmlspecialchars($mailToUrl);
5089  $res = '<a href="' . $mailToUrl . '"' . $aTagParams . '>';
5090  $wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
5091  if ((string)$conf['ATagBeforeWrap'] !== '') {
5092  $res = $res . $this->wrap($linktxt, $wrap) . '</a>';
5093  } else {
5094  $res = $this->wrap($res . $linktxt . '</a>', $wrap);
5095  }
5096  $textstr .= $res . $parts[1];
5097  } else {
5098  $textstr .= 'mailto:' . $textpieces[$i];
5099  }
5100  }
5101  return $textstr;
5102  }
5103 
5128  public function getImgResource($file, $fileArray)
5129  {
5130  if (empty($file) && empty($fileArray)) {
5131  return null;
5132  }
5133  if (!is_array($fileArray)) {
5134  $fileArray = (array)$fileArray;
5135  }
5136  $imageResource = null;
5137  $tsfe = $this->getTypoScriptFrontendController();
5138  if ($file === 'GIFBUILDER') {
5140  $gifCreator = GeneralUtility::makeInstance(GifBuilder::class);
5141  $gifCreator->init();
5142  $theImage = '';
5143  if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib']) {
5144  $gifCreator->start($fileArray, $this->data);
5145  $theImage = $gifCreator->gifBuild();
5146  }
5147  $imageResource = $gifCreator->getImageDimensions($theImage);
5148  $imageResource['origFile'] = $theImage;
5149  } else {
5150  if ($file instanceof File) {
5151  $fileObject = $file;
5152  } elseif ($file instanceof FileReference) {
5153  $fileObject = $file->getOriginalFile();
5154  if (!isset($fileArray['crop'])) {
5155  $fileArray['crop'] = $file->getProperty('crop');
5156  }
5157  } else {
5158  try {
5159  if ($fileArray['import.']) {
5160  $importedFile = trim($this->stdWrap('', $fileArray['import.']));
5161  if (!empty($importedFile)) {
5162  $file = $importedFile;
5163  }
5164  }
5165 
5167  $treatIdAsReference = isset($fileArray['treatIdAsReference.']) ? $this->stdWrap($fileArray['treatIdAsReference'], $fileArray['treatIdAsReference.']) : $fileArray['treatIdAsReference'];
5168  if (!empty($treatIdAsReference)) {
5169  $fileReference = $this->getResourceFactory()->getFileReferenceObject($file);
5170  $fileObject = $fileReference->getOriginalFile();
5171  if (!isset($fileArray['crop'])) {
5172  $fileArray['crop'] = $fileReference->getProperty('crop');
5173  }
5174  } else {
5175  $fileObject = $this->getResourceFactory()->getFileObject($file);
5176  }
5177  } elseif (preg_match('/^(0|[1-9][0-9]*):/', $file)) { // combined identifier
5178  $fileObject = $this->getResourceFactory()->retrieveFileOrFolderObject($file);
5179  } else {
5180  if (isset($importedFile) && !empty($importedFile) && !empty($fileArray['import'])) {
5181  $file = $fileArray['import'] . $file;
5182  }
5183  $fileObject = $this->getResourceFactory()->retrieveFileOrFolderObject($file);
5184  }
5185  } catch (Exception $exception) {
5187  $logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
5188  $logger->warning('The image "' . $file . '" could not be found and won\'t be included in frontend output', ['exception' => $exception]);
5189  return null;
5190  }
5191  }
5192  if ($fileObject instanceof File) {
5193  $processingConfiguration = [];
5194  $processingConfiguration['width'] = isset($fileArray['width.']) ? $this->stdWrap($fileArray['width'], $fileArray['width.']) : $fileArray['width'];
5195  $processingConfiguration['height'] = isset($fileArray['height.']) ? $this->stdWrap($fileArray['height'], $fileArray['height.']) : $fileArray['height'];
5196  $processingConfiguration['fileExtension'] = isset($fileArray['ext.']) ? $this->stdWrap($fileArray['ext'], $fileArray['ext.']) : $fileArray['ext'];
5197  $processingConfiguration['maxWidth'] = isset($fileArray['maxW.']) ? (int)$this->stdWrap($fileArray['maxW'], $fileArray['maxW.']) : (int)$fileArray['maxW'];
5198  $processingConfiguration['maxHeight'] = isset($fileArray['maxH.']) ? (int)$this->stdWrap($fileArray['maxH'], $fileArray['maxH.']) : (int)$fileArray['maxH'];
5199  $processingConfiguration['minWidth'] = isset($fileArray['minW.']) ? (int)$this->stdWrap($fileArray['minW'], $fileArray['minW.']) : (int)$fileArray['minW'];
5200  $processingConfiguration['minHeight'] = isset($fileArray['minH.']) ? (int)$this->stdWrap($fileArray['minH'], $fileArray['minH.']) : (int)$fileArray['minH'];
5201  $processingConfiguration['noScale'] = isset($fileArray['noScale.']) ? $this->stdWrap($fileArray['noScale'], $fileArray['noScale.']) : $fileArray['noScale'];
5202  $processingConfiguration['additionalParameters'] = isset($fileArray['params.']) ? $this->stdWrap($fileArray['params'], $fileArray['params.']) : $fileArray['params'];
5203  $processingConfiguration['frame'] = isset($fileArray['frame.']) ? (int)$this->stdWrap($fileArray['frame'], $fileArray['frame.']) : (int)$fileArray['frame'];
5204  $processingConfiguration['crop'] = isset($fileArray['crop.'])
5205  ? $this->stdWrap($fileArray['crop'], $fileArray['crop.'])
5206  : (isset($fileArray['crop']) ? $fileArray['crop'] : null);
5207  // Possibility to cancel/force profile extraction
5208  // see $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand']
5209  if (isset($fileArray['stripProfile'])) {
5210  $processingConfiguration['stripProfile'] = $fileArray['stripProfile'];
5211  }
5212  // Check if we can handle this type of file for editing
5213  if (GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], $fileObject->getExtension())) {
5214  $maskArray = $fileArray['m.'];
5215  // Must render mask images and include in hash-calculating
5216  // - otherwise we cannot be sure the filename is unique for the setup!
5217  if (is_array($maskArray)) {
5218  $mask = $this->getImgResource($maskArray['mask'], $maskArray['mask.']);
5219  $bgImg = $this->getImgResource($maskArray['bgImg'], $maskArray['bgImg.']);
5220  $bottomImg = $this->getImgResource($maskArray['bottomImg'], $maskArray['bottomImg.']);
5221  $bottomImg_mask = $this->getImgResource($maskArray['bottomImg_mask'], $maskArray['bottomImg_mask.']);
5222 
5223  $processingConfiguration['maskImages']['maskImage'] = $mask['processedFile'];
5224  $processingConfiguration['maskImages']['backgroundImage'] = $bgImg['processedFile'];
5225  $processingConfiguration['maskImages']['maskBottomImage'] = $bottomImg['processedFile'];
5226  $processingConfiguration['maskImages']['maskBottomImageMask'] = $bottomImg_mask['processedFile'];
5227  }
5228  $processedFileObject = $fileObject->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $processingConfiguration);
5229  $hash = $processedFileObject->calculateChecksum();
5230  // store info in the TSFE template cache (kept for backwards compatibility)
5231  if ($processedFileObject->isProcessed() && !isset($tsfe->tmpl->fileCache[$hash])) {
5232  $tsfe->tmpl->fileCache[$hash] = [
5233  0 => $processedFileObject->getProperty('width'),
5234  1 => $processedFileObject->getProperty('height'),
5235  2 => $processedFileObject->getExtension(),
5236  3 => $processedFileObject->getPublicUrl(),
5237  'origFile' => $fileObject->getPublicUrl(),
5238  'origFile_mtime' => $fileObject->getModificationTime(),
5239  // This is needed by \TYPO3\CMS\Frontend\Imaging\GifBuilder,
5240  // in order for the setup-array to create a unique filename hash.
5241  'originalFile' => $fileObject,
5242  'processedFile' => $processedFileObject,
5243  'fileCacheHash' => $hash
5244  ];
5245  }
5246  $imageResource = $tsfe->tmpl->fileCache[$hash];
5247  }
5248  }
5249  }
5250  // If image was processed by GIFBUILDER:
5251  // ($imageResource indicates that it was processed the regular way)
5252  if (!isset($imageResource)) {
5253  $theImage = $tsfe->tmpl->getFileName($file);
5254  if ($theImage) {
5255  $gifCreator = GeneralUtility::makeInstance(GifBuilder::class);
5257  $gifCreator->init();
5258  $info = $gifCreator->imageMagickConvert($theImage, 'WEB');
5259  $info['origFile'] = $theImage;
5260  // This is needed by \TYPO3\CMS\Frontend\Imaging\GifBuilder, ln 100ff in order for the setup-array to create a unique filename hash.
5261  $info['origFile_mtime'] = @filemtime($theImage);
5262  $imageResource = $info;
5263  }
5264  }
5265  // Hook 'getImgResource': Post-processing of image resources
5266  if (isset($imageResource)) {
5268  foreach ($this->getGetImgResourceHookObjects() as $hookObject) {
5269  $imageResource = $hookObject->getImgResourcePostProcess($file, (array)$fileArray, $imageResource, $this);
5270  }
5271  }
5272  return $imageResource;
5273  }
5274 
5275  /***********************************************
5276  *
5277  * Data retrieval etc.
5278  *
5279  ***********************************************/
5286  public function getFieldVal($field)
5287  {
5288  if (!strstr($field, '//')) {
5289  return $this->data[trim($field)];
5290  } else {
5291  $sections = GeneralUtility::trimExplode('//', $field, true);
5292  foreach ($sections as $k) {
5293  if ((string)$this->data[$k] !== '') {
5294  return $this->data[$k];
5295  }
5296  }
5297  }
5298  return '';
5299  }
5300 
5309  public function getData($string, $fieldArray = null)
5310  {
5311  $tsfe = $this->getTypoScriptFrontendController();
5312  if (!is_array($fieldArray)) {
5313  $fieldArray = $tsfe->page;
5314  }
5315  $retVal = '';
5316  $sections = explode('//', $string);
5317  foreach ($sections as $secKey => $secVal) {
5318  if ($retVal) {
5319  break;
5320  }
5321  $parts = explode(':', $secVal, 2);
5322  $type = strtolower(trim($parts[0]));
5323  $typesWithOutParameters = ['level', 'date', 'current', 'pagelayout'];
5324  $key = trim($parts[1]);
5325  if (($key != '') || in_array($type, $typesWithOutParameters)) {
5326  switch ($type) {
5327  case 'gp':
5328  // Merge GET and POST and get $key out of the merged array
5329  $getPostArray = GeneralUtility::_GET();
5331  $retVal = $this->getGlobal($key, $getPostArray);
5332  break;
5333  case 'tsfe':
5334  $retVal = $this->getGlobal('TSFE|' . $key);
5335  break;
5336  case 'getenv':
5337  $retVal = getenv($key);
5338  break;
5339  case 'getindpenv':
5340  $retVal = $this->getEnvironmentVariable($key);
5341  break;
5342  case 'field':
5343  $retVal = $this->getGlobal($key, $fieldArray);
5344  break;
5345  case 'file':
5346  $retVal = $this->getFileDataKey($key);
5347  break;
5348  case 'parameters':
5349  $retVal = $this->parameters[$key];
5350  break;
5351  case 'register':
5352  $retVal = $tsfe->register[$key];
5353  break;
5354  case 'global':
5355  $retVal = $this->getGlobal($key);
5356  break;
5357  case 'level':
5358  $retVal = count($tsfe->tmpl->rootLine) - 1;
5359  break;
5360  case 'leveltitle':
5361  $keyParts = GeneralUtility::trimExplode(',', $key);
5362  $numericKey = $this->getKey($keyParts[0], $tsfe->tmpl->rootLine);
5363  $retVal = $this->rootLineValue($numericKey, 'title', strtolower($keyParts[1]) === 'slide');
5364  break;
5365  case 'levelmedia':
5366  $keyParts = GeneralUtility::trimExplode(',', $key);
5367  $numericKey = $this->getKey($keyParts[0], $tsfe->tmpl->rootLine);
5368  $retVal = $this->rootLineValue($numericKey, 'media', strtolower($keyParts[1]) === 'slide');
5369  break;
5370  case 'leveluid':
5371  $numericKey = $this->getKey($key, $tsfe->tmpl->rootLine);
5372  $retVal = $this->rootLineValue($numericKey, 'uid');
5373  break;
5374  case 'levelfield':
5375  $keyParts = GeneralUtility::trimExplode(',', $key);
5376  $numericKey = $this->getKey($keyParts[0], $tsfe->tmpl->rootLine);
5377  $retVal = $this->rootLineValue($numericKey, $keyParts[1], strtolower($keyParts[2]) === 'slide');
5378  break;
5379  case 'fullrootline':
5380  $keyParts = GeneralUtility::trimExplode(',', $key);
5381  $fullKey = (int)$keyParts[0] - count($tsfe->tmpl->rootLine) + count($tsfe->rootLine);
5382  if ($fullKey >= 0) {
5383  $retVal = $this->rootLineValue($fullKey, $keyParts[1], stristr($keyParts[2], 'slide'), $tsfe->rootLine);
5384  }
5385  break;
5386  case 'date':
5387  if (!$key) {
5388  $key = 'd/m Y';
5389  }
5390  $retVal = date($key, $GLOBALS['EXEC_TIME']);
5391  break;
5392  case 'page':
5393  $retVal = $tsfe->page[$key];
5394  break;
5395  case 'pagelayout':
5396  // Check if the current page has a value in the DB field "backend_layout"
5397  // if empty, check the root line for "backend_layout_next_level"
5398  // same as
5399  // field = backend_layout
5400  // ifEmpty.data = levelfield:-2, backend_layout_next_level, slide
5401  // ifEmpty.ifEmpty = default
5402  $retVal = $GLOBALS['TSFE']->page['backend_layout'];
5403 
5404  // If it is set to "none" - don't use any
5405  if ($retVal === '-1') {
5406  $retVal = 'none';
5407  } elseif ($retVal === '' || $retVal === '0') {
5408  // If it not set check the root-line for a layout on next level and use this
5409  // Remove first element, which is the current page
5410  // See also \TYPO3\CMS\Backend\View\BackendLayoutView::getSelectedCombinedIdentifier()
5411  $rootLine = $tsfe->rootLine;
5412  array_shift($rootLine);
5413  foreach ($rootLine as $rootLinePage) {
5414  $retVal = (string) $rootLinePage['backend_layout_next_level'];
5415  // If layout for "next level" is set to "none" - don't use any and stop searching
5416  if ($retVal === '-1') {
5417  $retVal = 'none';
5418  break;
5419  } elseif ($retVal !== '' && $retVal !== '0') {
5420  // Stop searching if a layout for "next level" is set
5421  break;
5422  }
5423  }
5424  }
5425  if ($retVal === '0' || $retVal === '') {
5426  $retVal = 'default';
5427  }
5428  break;
5429  case 'current':
5430  $retVal = $this->data[$this->currentValKey];
5431  break;
5432  case 'db':
5433  $selectParts = GeneralUtility::trimExplode(':', $key);
5434  $db_rec = $tsfe->sys_page->getRawRecord($selectParts[0], $selectParts[1]);
5435  if (is_array($db_rec) && $selectParts[2]) {
5436  $retVal = $db_rec[$selectParts[2]];
5437  }
5438  break;
5439  case 'lll':
5440  $retVal = $tsfe->sL('LLL:' . $key);
5441  break;
5442  case 'path':
5443  $retVal = $tsfe->tmpl->getFileName($key);
5444  break;
5445  case 'cobj':
5446  switch ($key) {
5447  case 'parentRecordNumber':
5448  $retVal = $this->parentRecordNumber;
5449  break;
5450  }
5451  break;
5452  case 'debug':
5453  switch ($key) {
5454  case 'rootLine':
5455  $retVal = DebugUtility::viewArray($tsfe->tmpl->rootLine);
5456  break;
5457  case 'fullRootLine':
5458  $retVal = DebugUtility::viewArray($tsfe->rootLine);
5459  break;
5460  case 'data':
5461  $retVal = DebugUtility::viewArray($this->data);
5462  break;
5463  case 'register':
5464  $retVal = DebugUtility::viewArray($tsfe->register);
5465  break;
5466  case 'page':
5467  $retVal = DebugUtility::viewArray($tsfe->page);
5468  break;
5469  }
5470  break;
5471  case 'flexform':
5472  $keyParts = GeneralUtility::trimExplode(':', $key, true);
5473  if (count($keyParts) === 2 && isset($this->data[$keyParts[0]])) {
5474  $flexFormContent = $this->data[$keyParts[0]];
5475  if (!empty($flexFormContent)) {
5476  $flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
5477  $flexFormKey = str_replace('.', '|', $keyParts[1]);
5478  $settings = $flexFormService->convertFlexFormContentToArray($flexFormContent);
5479  $retVal = $this->getGlobal($flexFormKey, $settings);
5480  }
5481  }
5482  break;
5483  }
5484  }
5485  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getData'])) {
5486  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getData'] as $classData) {
5487  $hookObject = GeneralUtility::getUserObj($classData);
5488  if (!$hookObject instanceof ContentObjectGetDataHookInterface) {
5489  throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetDataHookInterface::class, 1195044480);
5490  }
5491  $retVal = $hookObject->getDataExtension($string, $fieldArray, $secVal, $retVal, $this);
5492  }
5493  }
5494  }
5495  return $retVal;
5496  }
5497 
5507  protected function getFileDataKey($key)
5508  {
5509  list($fileUidOrCurrentKeyword, $requestedFileInformationKey) = explode(':', $key, 3);
5510  try {
5511  if ($fileUidOrCurrentKeyword === 'current') {
5512  $fileObject = $this->getCurrentFile();
5513  } elseif (MathUtility::canBeInterpretedAsInteger($fileUidOrCurrentKeyword)) {
5515  $fileFactory = GeneralUtility::makeInstance(ResourceFactory::class);
5516  $fileObject = $fileFactory->getFileObject($fileUidOrCurrentKeyword);
5517  } else {
5518  $fileObject = null;
5519  }
5520  } catch (Exception $exception) {
5522  $logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
5523  $logger->warning('The file "' . $fileUidOrCurrentKeyword . '" could not be found and won\'t be included in frontend output', ['exception' => $exception]);
5524  $fileObject = null;
5525  }
5526 
5527  if ($fileObject instanceof FileInterface) {
5528  // All properties of the \TYPO3\CMS\Core\Resource\FileInterface are available here:
5529  switch ($requestedFileInformationKey) {
5530  case 'name':
5531  return $fileObject->getName();
5532  case 'uid':
5533  if (method_exists($fileObject, 'getUid')) {
5534  return $fileObject->getUid();
5535  }
5536  return 0;
5537  case 'originalUid':
5538  if ($fileObject instanceof FileReference) {
5539  return $fileObject->getOriginalFile()->getUid();
5540  }
5541  return null;
5542  case 'size':
5543  return $fileObject->getSize();
5544  case 'sha1':
5545  return $fileObject->getSha1();
5546  case 'extension':
5547  return $fileObject->getExtension();
5548  case 'mimetype':
5549  return $fileObject->getMimeType();
5550  case 'contents':
5551  return $fileObject->getContents();
5552  case 'publicUrl':
5553  return $fileObject->getPublicUrl();
5554  default:
5555  // Generic alternative here
5556  return $fileObject->getProperty($requestedFileInformationKey);
5557  }
5558  } else {
5559  // @todo fail silently as is common in tslib_content
5560  return 'Error: no file object';
5561  }
5562  }
5563 
5575  public function rootLineValue($key, $field, $slideBack = false, $altRootLine = '')
5576  {
5577  $rootLine = is_array($altRootLine) ? $altRootLine : $this->getTypoScriptFrontendController()->tmpl->rootLine;
5578  if (!$slideBack) {
5579  return $rootLine[$key][$field];
5580  } else {
5581  for ($a = $key; $a >= 0; $a--) {
5582  $val = $rootLine[$a][$field];
5583  if ($val) {
5584  return $val;
5585  }
5586  }
5587  }
5588  return '';
5589  }
5590 
5600  public function getGlobal($keyString, $source = null)
5601  {
5602  $keys = explode('|', $keyString);
5603  $numberOfLevels = count($keys);
5604  $rootKey = trim($keys[0]);
5605  $value = isset($source) ? $source[$rootKey] : $GLOBALS[$rootKey];
5606  for ($i = 1; $i < $numberOfLevels && isset($value); $i++) {
5607  $currentKey = trim($keys[$i]);
5608  if (is_object($value)) {
5609  $value = $value->{$currentKey};
5610  } elseif (is_array($value)) {
5611  $value = $value[$currentKey];
5612  } else {
5613  $value = '';
5614  break;
5615  }
5616  }
5617  if (!is_scalar($value)) {
5618  $value = '';
5619  }
5620  return $value;
5621  }
5622 
5633  public function getKey($key, $arr)
5634  {
5635  $key = (int)$key;
5636  if (is_array($arr)) {
5637  if ($key < 0) {
5638  $key = count($arr) + $key;
5639  }
5640  if ($key < 0) {
5641  $key = 0;
5642  }
5643  }
5644  return $key;
5645  }
5646 
5656  public function TCAlookup($inputValue, $conf)
5657  {
5658  $table = $conf['table'];
5659  $field = $conf['field'];
5660  $delimiter = $conf['delimiter'] ? $conf['delimiter'] : ' ,';
5661  if (is_array($GLOBALS['TCA'][$table]) && is_array($GLOBALS['TCA'][$table]['columns'][$field]) && is_array($GLOBALS['TCA'][$table]['columns'][$field]['config']['items'])) {
5662  $tsfe = $this->getTypoScriptFrontendController();
5663  $values = GeneralUtility::trimExplode(',', $inputValue);
5664  $output = [];
5665  foreach ($values as $value) {
5666  // Traverse the items-array...
5667  foreach ($GLOBALS['TCA'][$table]['columns'][$field]['config']['items'] as $item) {
5668  // ... and return the first found label where the value was equal to $key
5669  if ((string)$item[1] === trim($value)) {
5670  $output[] = $tsfe->sL($item[0]);
5671  }
5672  }
5673  }
5674  $returnValue = implode($delimiter, $output);
5675  } else {
5676  $returnValue = $inputValue;
5677  }
5678  return $returnValue;
5679  }
5680 
5681  /***********************************************
5682  *
5683  * Link functions (typolink)
5684  *
5685  ***********************************************/
5686 
5699  protected function resolveMixedLinkParameter($linkText, $mixedLinkParameter, &$configuration = [])
5700  {
5701  $linkParameter = null;
5702 
5703  // Link parameter value = first part
5704  $linkParameterParts = GeneralUtility::makeInstance(TypoLinkCodecService::class)->decode($mixedLinkParameter);
5705 
5706  // Check for link-handler keyword:
5707  list($linkHandlerKeyword, $linkHandlerValue) = explode(':', $linkParameterParts['url'], 2);
5708  if ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['typolinkLinkHandler'][$linkHandlerKeyword] && (string)$linkHandlerValue !== '') {
5709  $linkHandlerObj = GeneralUtility::getUserObj($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['typolinkLinkHandler'][$linkHandlerKeyword]);
5710  if (method_exists($linkHandlerObj, 'main')) {
5711  return $linkHandlerObj->main($linkText, $configuration, $linkHandlerKeyword, $linkHandlerValue, $mixedLinkParameter, $this);
5712  }
5713  }
5714 
5715  // Resolve FAL-api "file:UID-of-sys_file-record" and "file:combined-identifier"
5716  if ($linkHandlerKeyword === 'file' && strpos($linkParameterParts['url'], 'file://') !== 0) {
5717  try {
5718  $fileOrFolderObject = $this->getResourceFactory()->retrieveFileOrFolderObject($linkHandlerValue);
5719  // Link to a folder or file
5720  if ($fileOrFolderObject instanceof File || $fileOrFolderObject instanceof Folder) {
5721  $linkParameter = $fileOrFolderObject->getPublicUrl();
5722  } else {
5723  $linkParameter = null;
5724  }
5725  } catch (\RuntimeException $e) {
5726  // Element wasn't found
5727  $linkParameter = null;
5728  } catch (ResourceDoesNotExistException $e) {
5729  // Resource was not found
5730  return $linkText;
5731  }
5732  // Disallow direct javascript: or data: links
5733  } elseif (in_array(strtolower(trim($linkHandlerKeyword)), ['javascript', 'data'], true)) {
5734  return $linkText;
5735  } else {
5736  $linkParameter = $linkParameterParts['url'];
5737  }
5738 
5739  // additional parameters that need to be set
5740  if ($linkParameterParts['additionalParams'] !== '') {
5741  $forceParams = $linkParameterParts['additionalParams'];
5742  // params value
5743  $configuration['additionalParams'] .= $forceParams[0] === '&' ? $forceParams : '&' . $forceParams;
5744  }
5745 
5746  return [
5747  'href' => $linkParameter,
5748  'target' => $linkParameterParts['target'],
5749  'class' => $linkParameterParts['class'],
5750  'title' => $linkParameterParts['title']
5751  ];
5752  }
5753 
5768  public function typoLink($linkText, $conf)
5769  {
5770  $linkText = (string)$linkText;
5771  $tsfe = $this->getTypoScriptFrontendController();
5772 
5773  $LD = [];
5774  $finalTagParts = [];
5775  $finalTagParts['aTagParams'] = $this->getATagParams($conf);
5776  $linkParameter = trim(isset($conf['parameter.']) ? $this->stdWrap($conf['parameter'], $conf['parameter.']) : $conf['parameter']);
5777  $this->lastTypoLinkUrl = '';
5778  $this->lastTypoLinkTarget = '';
5779 
5780  $resolvedLinkParameters = $this->resolveMixedLinkParameter($linkText, $linkParameter, $conf);
5781  // check if the link handler hook has resolved the link completely already
5782  if (!is_array($resolvedLinkParameters)) {
5783  return $resolvedLinkParameters;
5784  }
5785 
5786  $linkParameter = $resolvedLinkParameters['href'];
5787  $target = $resolvedLinkParameters['target'];
5788  $linkClass = $resolvedLinkParameters['class'];
5789  $title = $resolvedLinkParameters['title'];
5790 
5791  if (!$linkParameter) {
5792  return $linkText;
5793  }
5794 
5795  // Check, if the target is coded as a JS open window link:
5796  $JSwindowParts = [];
5797  $JSwindowParams = '';
5798  if ($target && preg_match('/^([0-9]+)x([0-9]+)(:(.*)|.*)$/', $target, $JSwindowParts)) {
5799  // Take all pre-configured and inserted parameters and compile parameter list, including width+height:
5800  $JSwindow_tempParamsArr = GeneralUtility::trimExplode(',', strtolower($conf['JSwindow_params'] . ',' . $JSwindowParts[4]), true);
5801  $JSwindow_paramsArr = [];
5802  foreach ($JSwindow_tempParamsArr as $JSv) {
5803  list($JSp, $JSv) = explode('=', $JSv, 2);
5804  $JSwindow_paramsArr[$JSp] = $JSp . '=' . $JSv;
5805  }
5806  // Add width/height:
5807  $JSwindow_paramsArr['width'] = 'width=' . $JSwindowParts[1];
5808  $JSwindow_paramsArr['height'] = 'height=' . $JSwindowParts[2];
5809  // Imploding into string:
5810  $JSwindowParams = implode(',', $JSwindow_paramsArr);
5811  // Resetting the target since we will use onClick.
5812  $target = '';
5813  }
5814 
5815  // Detecting kind of link and resolve all necessary parameters
5817  $linkService = GeneralUtility::makeInstance(LinkService::class);
5818  $linkDetails = $linkService->resolve($linkParameter);
5819  switch ($linkDetails['type']) {
5820  // If it's a mail address
5821  case LinkService::TYPE_EMAIL:
5822  list($this->lastTypoLinkUrl, $linkText) = $this->getMailTo($linkDetails['email'], $linkText);
5823  $finalTagParts['url'] = $this->lastTypoLinkUrl;
5824  break;
5825 
5826  // URL (external)
5827  case LinkService::TYPE_URL:
5828  if (empty($target)) {
5829  if (isset($conf['extTarget'])) {
5830  $target = $conf['extTarget'];
5831  } elseif ($tsfe->dtdAllowsFrames) {
5832  $target = $tsfe->extTarget;
5833  }
5834  if ($conf['extTarget.']) {
5835  $target = $this->stdWrap($target, $conf['extTarget.']);
5836  }
5837  }
5838  $linkText = $this->parseFallbackLinkTextIfLinkTextIsEmpty($linkText, $linkDetails['url']);
5839 
5840  $this->lastTypoLinkUrl = $this->processUrl(UrlProcessorInterface::CONTEXT_EXTERNAL, $linkDetails['url'], $conf);
5841  $this->lastTypoLinkTarget = $target;
5842  $finalTagParts['url'] = $this->lastTypoLinkUrl;
5843  $finalTagParts['targetParams'] = $target ? ' target="' . htmlspecialchars($target) . '"' : '';
5844  $finalTagParts['aTagParams'] .= $this->extLinkATagParams($finalTagParts['url'], LinkService::TYPE_URL);
5845  break;
5846 
5847  // File (internal)
5848  case LinkService::TYPE_FILE:
5849  case LinkService::TYPE_FOLDER:
5850  $fileOrFolderObject = $linkDetails['file'] ? $linkDetails['file'] : $linkDetails['folder'];
5851  // check if the file exists or if a / is contained (same check as in detectLinkType)
5852  if ($fileOrFolderObject instanceof FileInterface || $fileOrFolderObject instanceof Folder) {
5853  $linkLocation = $fileOrFolderObject->getPublicUrl();
5854  // Setting title if blank value to link
5855  $linkText = $this->parseFallbackLinkTextIfLinkTextIsEmpty($linkText, rawurldecode($linkLocation));
5856  $linkLocation = (strpos($linkLocation, '/') !== 0 ? $tsfe->absRefPrefix : '') . $linkLocation;
5857  $this->lastTypoLinkUrl = $this->processUrl(UrlProcessorInterface::CONTEXT_FILE, $linkLocation, $conf);
5858  $this->lastTypoLinkUrl = $this->forceAbsoluteUrl($this->lastTypoLinkUrl, $conf);
5859 
5860  if (empty($target)) {
5861  $target = isset($conf['fileTarget']) ? $conf['fileTarget'] : $tsfe->fileTarget;
5862  if ($conf['fileTarget.']) {
5863  $target = $this->stdWrap($target, $conf['fileTarget.']);
5864  }
5865  }
5866  $this->lastTypoLinkTarget = $target;
5867  $finalTagParts['url'] = $this->lastTypoLinkUrl;
5868  $finalTagParts['targetParams'] = $target ? ' target="' . htmlspecialchars($target) . '"' : '';
5869  $finalTagParts['aTagParams'] .= $this->extLinkATagParams($finalTagParts['url'], LinkService::TYPE_FILE);
5870  } else {
5871  $this->getTimeTracker()->setTSlogMessage('typolink(): File "' . $linkParameter . '" did not exist, so "' . $linkText . '" was not linked.', 1);
5872  return $linkText;
5873  }
5874  break;
5875 
5876  // Link to a page
5877  case LinkService::TYPE_PAGE:
5878  $enableLinksAcrossDomains = $tsfe->config['config']['typolinkEnableLinksAcrossDomains'];
5879  if ($conf['no_cache.']) {
5880  $conf['no_cache'] = $this->stdWrap($conf['no_cache'], $conf['no_cache.']);
5881  }
5882  // Checking if the id-parameter is an alias.
5883  if (!empty($linkDetails['pagealias'])) {
5884  $linkDetails['pageuid'] = $tsfe->sys_page->getPageIdFromAlias($linkDetails['pagealias']);
5885  } elseif (empty($linkDetails['pageuid']) || $linkDetails['pageuid'] === 'current') {
5886  // If no id or alias is given
5887  $linkDetails['pageuid'] = $tsfe->id;
5888  }
5889  $sectionMark = trim(isset($conf['section.']) ? $this->stdWrap($conf['section'], $conf['section.']) : $conf['section']);
5890  if ($sectionMark === '' && isset($linkDetails['fragment'])) {
5891  $sectionMark = $linkDetails['fragment'];
5892  }
5893  if ($sectionMark !== '') {
5894  $sectionMark = '#' . (MathUtility::canBeInterpretedAsInteger($sectionMark) ? 'c' : '') . $sectionMark;
5895  }
5896  // Overruling 'type'
5897  $pageType = $linkDetails['pagetype'] ?? 0;
5898 
5899  if (isset($linkDetails['parameters'])) {
5900  $conf['additionalParams'] .= '&' . ltrim($linkDetails['parameters'], '&');
5901  }
5902 
5903  // Link to page even if access is missing?
5904  if (isset($conf['linkAccessRestrictedPages'])) {
5905  $disableGroupAccessCheck = (bool)$conf['linkAccessRestrictedPages'];
5906  } else {
5907  $disableGroupAccessCheck = (bool)$tsfe->config['config']['typolinkLinkAccessRestrictedPages'];
5908  }
5909  // Looking up the page record to verify its existence:
5910  $page = $tsfe->sys_page->getPage($linkDetails['pageuid'], $disableGroupAccessCheck);
5911  if (!empty($page)) {
5912  // MointPoints, look for closest MPvar:
5913  $MPvarAcc = [];
5914  if (!$tsfe->config['config']['MP_disableTypolinkClosestMPvalue']) {
5915  $temp_MP = $this->getClosestMPvalueForPage($page['uid'], true);
5916  if ($temp_MP) {
5917  $MPvarAcc['closest'] = $temp_MP;
5918  }
5919  }
5920  // Look for overlay Mount Point:
5921  $mount_info = $tsfe->sys_page->getMountPointInfo($page['uid'], $page);
5922  if (is_array($mount_info) && $mount_info['overlay']) {
5923  $page = $tsfe->sys_page->getPage($mount_info['mount_pid'], $disableGroupAccessCheck);
5924  if (empty($page)) {
5925  $this->getTimeTracker()->setTSlogMessage('typolink(): Mount point "' . $mount_info['mount_pid'] . '" was not available, so "' . $linkText . '" was not linked.', 1);
5926  return $linkText;
5927  }
5928  $MPvarAcc['re-map'] = $mount_info['MPvar'];
5929  }
5930  // Setting title if blank value to link
5931  $linkText = $this->parseFallbackLinkTextIfLinkTextIsEmpty($linkText, $page['title']);
5932  // Query Params:
5933  $addQueryParams = $conf['addQueryString'] ? $this->getQueryArguments($conf['addQueryString.']) : '';
5934  $addQueryParams .= isset($conf['additionalParams.']) ? trim($this->stdWrap($conf['additionalParams'], $conf['additionalParams.'])) : trim($conf['additionalParams']);
5935  if ($addQueryParams === '&' || $addQueryParams[0] !== '&') {
5936  $addQueryParams = '';
5937  }
5938  $targetDomain = '';
5939  $currentDomain = (string)$this->getEnvironmentVariable('HTTP_HOST');
5940  // Mount pages are always local and never link to another domain
5941  if (!empty($MPvarAcc)) {
5942  // Add "&MP" var:
5943  $addQueryParams .= '&MP=' . rawurlencode(implode(',', $MPvarAcc));
5944  } elseif (strpos($addQueryParams, '&MP=') === false && $tsfe->config['config']['typolinkCheckRootline']) {
5945  // We do not come here if additionalParams had '&MP='. This happens when typoLink is called from
5946  // menu. Mount points always work in the content of the current domain and we must not change
5947  // domain if MP variables exist.
5948  // If we link across domains and page is free type shortcut, we must resolve the shortcut first!
5949  // If we do not do it, TYPO3 will fail to (1) link proper page in RealURL/CoolURI because
5950  // they return relative links and (2) show proper page if no RealURL/CoolURI exists when link is clicked
5951  if ($enableLinksAcrossDomains
5952  && (int)$page['doktype'] === PageRepository::DOKTYPE_SHORTCUT
5953  && (int)$page['shortcut_mode'] === PageRepository::SHORTCUT_MODE_NONE
5954  ) {
5955  // Save in case of broken destination or endless loop
5956  $page2 = $page;
5957  // Same as in RealURL, seems enough
5958  $maxLoopCount = 20;
5959  while ($maxLoopCount
5960  && is_array($page)
5961  && (int)$page['doktype'] === PageRepository::DOKTYPE_SHORTCUT
5962  && (int)$page['shortcut_mode'] === PageRepository::SHORTCUT_MODE_NONE
5963  ) {
5964  $page = $tsfe->sys_page->getPage($page['shortcut'], $disableGroupAccessCheck);
5965  $maxLoopCount--;
5966  }
5967  if (empty($page) || $maxLoopCount === 0) {
5968  // We revert if shortcut is broken or maximum number of loops is exceeded (indicates endless loop)
5969  $page = $page2;
5970  }
5971  }
5972 
5973  $targetDomain = $tsfe->getDomainNameForPid($page['uid']);
5974  // Do not prepend the domain if it is the current hostname
5975  if (!$targetDomain || $tsfe->domainNameMatchesCurrentRequest($targetDomain)) {
5976  $targetDomain = '';
5977  }
5978  }
5979  if ($conf['useCacheHash']) {
5980  $params = $tsfe->linkVars . $addQueryParams . '&id=' . $page['uid'];
5981  if (trim($params, '& ') != '') {
5983  $cacheHash = GeneralUtility::makeInstance(CacheHashCalculator::class);
5984  $cHash = $cacheHash->generateForParameters($params);
5985  $addQueryParams .= $cHash ? '&cHash=' . $cHash : '';
5986  }
5987  unset($params);
5988  }
5989  $absoluteUrlScheme = 'http';
5990  // URL shall be absolute:
5991  if (isset($conf['forceAbsoluteUrl']) && $conf['forceAbsoluteUrl'] || $page['url_scheme'] > 0) {
5992  // Override scheme:
5993  if (isset($conf['forceAbsoluteUrl.']['scheme']) && $conf['forceAbsoluteUrl.']['scheme']) {
5994  $absoluteUrlScheme = $conf['forceAbsoluteUrl.']['scheme'];
5995  } elseif ($page['url_scheme'] > 0) {
5996  $absoluteUrlScheme = (int)$page['url_scheme'] === HttpUtility::SCHEME_HTTP ? 'http' : 'https';
5997  } elseif ($this->getEnvironmentVariable('TYPO3_SSL')) {
5998  $absoluteUrlScheme = 'https';
5999  }
6000  // If no domain records are defined, use current domain:
6001  $currentUrlScheme = parse_url($this->getEnvironmentVariable('TYPO3_REQUEST_URL'), PHP_URL_SCHEME);
6002  if ($targetDomain === '' && ($conf['forceAbsoluteUrl'] || $absoluteUrlScheme !== $currentUrlScheme)) {
6003  $targetDomain = $currentDomain;
6004  }
6005  // If go for an absolute link, add site path if it's not taken care about by absRefPrefix
6006  if (!$tsfe->config['config']['absRefPrefix'] && $targetDomain === $currentDomain) {
6007  $targetDomain = $currentDomain . rtrim($this->getEnvironmentVariable('TYPO3_SITE_PATH'), '/');
6008  }
6009  }
6010  // If target page has a different domain and the current domain's linking scheme (e.g. RealURL/...) should not be used
6011  if ($targetDomain !== '' && $targetDomain !== $currentDomain && !$enableLinksAcrossDomains) {
6012  if (empty($target)) {
6013  $target = isset($conf['extTarget']) ? $conf['extTarget'] : $tsfe->extTarget;
6014  if ($conf['extTarget.']) {
6015  $target = $this->stdWrap($target, $conf['extTarget.']);
6016  }
6017  }
6018  $LD['target'] = $target;
6019  // Convert IDNA-like domain (if any)
6020  if (!preg_match('/^[a-z0-9.\\-]*$/i', $targetDomain)) {
6021  $targetDomain = GeneralUtility::idnaEncode($targetDomain);
6022  }
6023  $this->lastTypoLinkUrl = $this->URLqMark($absoluteUrlScheme . '://' . $targetDomain . '/index.php?id=' . $page['uid'], $addQueryParams) . $sectionMark;
6024  } else {
6025  // Internal link or current domain's linking scheme should be used
6026  // Internal target:
6027  if (empty($target)) {
6028  if (isset($conf['target'])) {
6029  $target = $conf['target'];
6030  } elseif ($tsfe->dtdAllowsFrames) {
6031  $target = $tsfe->intTarget;
6032  }
6033  if ($conf['target.']) {
6034  $target = $this->stdWrap($target, $conf['target.']);
6035  }
6036  }
6037  $LD = $tsfe->tmpl->linkData($page, $target, $conf['no_cache'], '', '', $addQueryParams, $pageType, $targetDomain);
6038  if ($targetDomain !== '') {
6039  // We will add domain only if URL does not have it already.
6040  if ($enableLinksAcrossDomains && $targetDomain !== $currentDomain) {
6041  // Get rid of the absRefPrefix if necessary. absRefPrefix is applicable only
6042  // to the current web site. If we have domain here it means we link across
6043  // domains. absRefPrefix can contain domain name, which will screw up
6044  // the link to the external domain.
6045  $prefixLength = strlen($tsfe->config['config']['absRefPrefix']);
6046  if (substr($LD['totalURL'], 0, $prefixLength) === $tsfe->config['config']['absRefPrefix']) {
6047  $LD['totalURL'] = substr($LD['totalURL'], $prefixLength);
6048  }
6049  }
6050  $urlParts = parse_url($LD['totalURL']);
6051  if (empty($urlParts['host'])) {
6052  $LD['totalURL'] = $absoluteUrlScheme . '://' . $targetDomain . ($LD['totalURL'][0] === '/' ? '' : '/') . $LD['totalURL'];
6053  }
6054  }
6055  $this->lastTypoLinkUrl = $LD['totalURL'] . $sectionMark;
6056  }
6057  $this->lastTypoLinkTarget = $LD['target'];
6058  // If sectionMark is set, there is no baseURL AND the current page is the page the link is to, check if there are any additional parameters or addQueryString parameters and if not, drop the url.
6059  if ($sectionMark
6060  && !$tsfe->config['config']['baseURL']
6061  && (int)$page['uid'] === (int)$tsfe->id
6062  && !trim($addQueryParams)
6063  && (empty($conf['addQueryString']) || !isset($conf['addQueryString.']))
6064  ) {
6065  $currentQueryParams = $this->getQueryArguments([]);
6066  if (!trim($currentQueryParams)) {
6067  list(, $URLparams) = explode('?', $this->lastTypoLinkUrl);
6068  list($URLparams) = explode('#', $URLparams);
6069  parse_str($URLparams . $LD['orig_type'], $URLparamsArray);
6070  // Type nums must match as well as page ids
6071  if ((int)$URLparamsArray['type'] === (int)$tsfe->type) {
6072  unset($URLparamsArray['id']);
6073  unset($URLparamsArray['type']);
6074  // If there are no parameters left.... set the new url.
6075  if (empty($URLparamsArray)) {
6076  $this->lastTypoLinkUrl = $sectionMark;
6077  }
6078  }
6079  }
6080  }
6081  // If link is to an access restricted page which should be redirected, then find new URL:
6082  if (empty($conf['linkAccessRestrictedPages'])
6083  && $tsfe->config['config']['typolinkLinkAccessRestrictedPages']
6084  && $tsfe->config['config']['typolinkLinkAccessRestrictedPages'] !== 'NONE'
6085  && !$tsfe->checkPageGroupAccess($page)
6086  ) {
6087  $thePage = $tsfe->sys_page->getPage($tsfe->config['config']['typolinkLinkAccessRestrictedPages']);
6088  $addParams = str_replace(
6089  [
6090  '###RETURN_URL###',
6091  '###PAGE_ID###'
6092  ],
6093  [
6094  rawurlencode($this->lastTypoLinkUrl),
6095  $page['uid']
6096  ],
6097  $tsfe->config['config']['typolinkLinkAccessRestrictedPages_addParams']
6098  );
6099  $this->lastTypoLinkUrl = $this->getTypoLink_URL($thePage['uid'] . ($pageType ? ',' . $pageType : ''), $addParams, $target);
6100  $this->lastTypoLinkUrl = $this->forceAbsoluteUrl($this->lastTypoLinkUrl, $conf);
6101  $this->lastTypoLinkLD['totalUrl'] = $this->lastTypoLinkUrl;
6102  $LD = $this->lastTypoLinkLD;
6103  }
6104  // Rendering the tag.
6105  $finalTagParts['url'] = $this->lastTypoLinkUrl;
6106  $finalTagParts['targetParams'] = (string)$LD['target'] !== '' ? ' target="' . htmlspecialchars($LD['target']) . '"' : '';
6107  } else {
6108  $this->getTimeTracker()->setTSlogMessage('typolink(): Page id "' . $linkParameter . '" was not found, so "' . $linkText . '" was not linked.', 1);
6109  return $linkText;
6110  }
6111  break;
6112 
6113  // Legacy files or something else
6114  case LinkService::TYPE_UNKNOWN:
6115  if ($linkDetails['file']) {
6116  $linkLocation = $linkDetails['file'];
6117  // Setting title if blank value to link
6118  $linkText = $this->parseFallbackLinkTextIfLinkTextIsEmpty($linkText, rawurldecode($linkLocation));
6119  $linkLocation = (strpos($linkLocation, '/') !== 0 ? $tsfe->absRefPrefix : '') . $linkLocation;
6120  $this->lastTypoLinkUrl = $this->processUrl(UrlProcessorInterface::CONTEXT_FILE, $linkLocation, $conf);
6121  $this->lastTypoLinkUrl = $this->forceAbsoluteUrl($this->lastTypoLinkUrl, $conf);
6122  if (empty($target)) {
6123  $target = isset($conf['fileTarget']) ? $conf['fileTarget'] : $tsfe->fileTarget;
6124  if ($conf['fileTarget.']) {
6125  $target = $this->stdWrap($target, $conf['fileTarget.']);
6126  }
6127  }
6128  $this->lastTypoLinkTarget = $target;
6129  $finalTagParts['url'] = $this->lastTypoLinkUrl;
6130  $finalTagParts['targetParams'] = $target ? ' target="' . $target . '"' : '';
6131  $finalTagParts['aTagParams'] .= $this->extLinkATagParams($finalTagParts['url'], LinkService::TYPE_FILE);
6132  } elseif ($linkDetails['url']) {
6133  if (empty($target)) {
6134  if (isset($conf['extTarget'])) {
6135  $target = $conf['extTarget'];
6136  } elseif ($tsfe->dtdAllowsFrames) {
6137  $target = $tsfe->extTarget;
6138  }
6139  if ($conf['extTarget.']) {
6140  $target = $this->stdWrap($target, $conf['extTarget.']);
6141  }
6142  }
6143  $linkText = $this->parseFallbackLinkTextIfLinkTextIsEmpty($linkText, $linkDetails['url']);
6144 
6145  $this->lastTypoLinkUrl = $this->processUrl(UrlProcessorInterface::CONTEXT_EXTERNAL, $linkDetails['url'], $conf);
6146  $this->lastTypoLinkTarget = $target;
6147  $finalTagParts['url'] = $this->lastTypoLinkUrl;
6148  $finalTagParts['targetParams'] = $target ? ' target="' . $target . '"' : '';
6149  $finalTagParts['aTagParams'] .= $this->extLinkATagParams($finalTagParts['url'], LinkService::TYPE_URL);
6150  }
6151  break;
6152  }
6153 
6154  $finalTagParts['TYPE'] = $linkDetails['type'];
6155  $this->lastTypoLinkLD = $LD;
6156 
6157  // Title tag
6158  if (empty($title)) {
6159  $title = $conf['title'];
6160  if ($conf['title.']) {
6161  $title = $this->stdWrap($title, $conf['title.']);
6162  }
6163  }
6164 
6165  if ($JSwindowParams) {
6166  // Create TARGET-attribute only if the right doctype is used
6167  $xhtmlDocType = $tsfe->xhtmlDoctype;
6168  if ($xhtmlDocType !== 'xhtml_strict' && $xhtmlDocType !== 'xhtml_11') {
6169  $target = ' target="FEopenLink"';
6170  } else {
6171  $target = '';
6172  }
6173  $onClick = 'vHWin=window.open(' . GeneralUtility::quoteJSvalue($tsfe->baseUrlWrap($finalTagParts['url'])) . ',\'FEopenLink\',' . GeneralUtility::quoteJSvalue($JSwindowParams) . ');vHWin.focus();return false;';
6174  $finalAnchorTag = '<a href="' . htmlspecialchars($finalTagParts['url']) . '"'
6175  . $target
6176  . ' onclick="' . htmlspecialchars($onClick) . '"'
6177  . ((string)$title !== '' ? ' title="' . htmlspecialchars($title) . '"' : '')
6178  . ($linkClass !== '' ? ' class="' . htmlspecialchars($linkClass) . '"' : '')
6179  . $finalTagParts['aTagParams']
6180  . '>';
6181  } else {
6182  if ($tsfe->spamProtectEmailAddresses === 'ascii' && $linkDetails['type'] === LinkService::TYPE_EMAIL) {
6183  $finalAnchorTag = '<a href="' . $finalTagParts['url'] . '"';
6184  } else {
6185  $finalAnchorTag = '<a href="' . htmlspecialchars($finalTagParts['url']) . '"';
6186  }
6187  $finalAnchorTag .= ((string)$title !== '' ? ' title="' . htmlspecialchars($title) . '"' : '')
6188  . $finalTagParts['targetParams']
6189  . ($linkClass ? ' class="' . htmlspecialchars($linkClass) . '"' : '')
6190  . $finalTagParts['aTagParams']
6191  . '>';
6192  }
6193 
6194  // Call user function:
6195  if ($conf['userFunc']) {
6196  $finalTagParts['TAG'] = $finalAnchorTag;
6197  $finalAnchorTag = $this->callUserFunction($conf['userFunc'], $conf['userFunc.'], $finalTagParts);
6198  }
6199 
6200  // Hook: Call post processing function for link rendering:
6201  if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['typoLink_PostProc']) && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['typoLink_PostProc'])) {
6202  $_params = [
6203  'conf' => &$conf,
6204  'linktxt' => &$linkText,
6205  'finalTag' => &$finalAnchorTag,
6206  'finalTagParts' => &$finalTagParts
6207  ];
6208  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['typoLink_PostProc'] as $_funcRef) {
6209  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
6210  }
6211  }
6212 
6213  // If flag "returnLastTypoLinkUrl" set, then just return the latest URL made:
6214  if ($conf['returnLast']) {
6215  switch ($conf['returnLast']) {
6216  case 'url':
6217  return $this->lastTypoLinkUrl;
6218  break;
6219  case 'target':
6220  return $this->lastTypoLinkTarget;
6221  break;
6222  }
6223  }
6224 
6225  $wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
6226 
6227  if ($conf['ATagBeforeWrap']) {
6228  return $finalAnchorTag . $this->wrap($linkText, $wrap) . '</a>';
6229  }
6230  return $this->wrap($finalAnchorTag . $linkText . '</a>', $wrap);
6231  }
6232 
6240  protected function parseFallbackLinkTextIfLinkTextIsEmpty($originalLinkText, $fallbackLinkText)
6241  {
6242  if ($originalLinkText === '') {
6243  return $this->parseFunc($fallbackLinkText, ['makelinks' => 0], '< lib.parseFunc');
6244  } else {
6245  return $originalLinkText;
6246  }
6247  }
6248 
6256  protected function forceAbsoluteUrl($url, array $configuration)
6257  {
6258  if (!empty($url) && !empty($configuration['forceAbsoluteUrl']) && preg_match('#^(?:([a-z]+)(://)([^/]*)/?)?(.*)$#', $url, $matches)) {
6259  $urlParts = [
6260  'scheme' => $matches[1],
6261  'delimiter' => '://',
6262  'host' => $matches[3],
6263  'path' => $matches[4]
6264  ];
6265  $isUrlModified = false;
6266  // Set scheme and host if not yet part of the URL:
6267  if (empty($urlParts['host'])) {
6268  $urlParts['scheme'] = $this->getEnvironmentVariable('TYPO3_SSL') ? 'https' : 'http';
6269  $urlParts['host'] = $this->getEnvironmentVariable('HTTP_HOST');
6270  $urlParts['path'] = '/' . ltrim($urlParts['path'], '/');
6271  // absRefPrefix has been prepended to $url beforehand
6272  // so we only modify the path if no absRefPrefix has been set
6273  // otherwise we would destroy the path
6274  if ($this->getTypoScriptFrontendController()->absRefPrefix === '') {
6275  $urlParts['path'] = $this->getEnvironmentVariable('TYPO3_SITE_PATH') . ltrim($urlParts['path'], '/');
6276  }
6277  $isUrlModified = true;
6278  }
6279  // Override scheme:
6280  $forceAbsoluteUrl = &$configuration['forceAbsoluteUrl.']['scheme'];
6281  if (!empty($forceAbsoluteUrl) && $urlParts['scheme'] !== $forceAbsoluteUrl) {
6282  $urlParts['scheme'] = $forceAbsoluteUrl;
6283  $isUrlModified = true;
6284  }
6285  // Recreate the absolute URL:
6286  if ($isUrlModified) {
6287  $url = implode('', $urlParts);
6288  }
6289  }
6290  return $url;
6291  }
6292 
6300  public function typoLink_URL($conf)
6301  {
6302  $this->typoLink('|', $conf);
6303  return $this->lastTypoLinkUrl;
6304  }
6305 
6319  public function getTypoLink($label, $params, $urlParameters = [], $target = '')
6320  {
6321  $conf = [];
6322  $conf['parameter'] = $params;
6323  if ($target) {
6324  $conf['target'] = $target;
6325  $conf['extTarget'] = $target;
6326  $conf['fileTarget'] = $target;
6327  }
6328  if (is_array($urlParameters)) {
6329  if (!empty($urlParameters)) {
6330  $conf['additionalParams'] .= GeneralUtility::implodeArrayForUrl('', $urlParameters);
6331  }
6332  } else {
6333  $conf['additionalParams'] .= $urlParameters;
6334  }
6335  $out = $this->typoLink($label, $conf);
6336  return $out;
6337  }
6338 
6346  public function getUrlToCurrentLocation($addQueryString = true)
6347  {
6348  $conf = [];
6349  $conf['parameter'] = $this->getTypoScriptFrontendController()->id . ',' . $this->getTypoScriptFrontendController()->type;
6350  if ($addQueryString) {
6351  $conf['addQueryString'] = '1';
6352  $linkVars = implode(',', array_keys(GeneralUtility::explodeUrl2Array($this->getTypoScriptFrontendController()->linkVars)));
6353  $conf['addQueryString.'] = [
6354  'method' => 'GET',
6355  'exclude' => 'id,type,cHash' . ($linkVars ? ',' . $linkVars : '')
6356  ];
6357  $conf['useCacheHash'] = GeneralUtility::_GET('cHash') ? '1' : '0';
6358  }
6359 
6360  return $this->typoLink_URL($conf);
6361  }
6362 
6372  public function getTypoLink_URL($params, $urlParameters = [], $target = '')
6373  {
6374  $this->getTypoLink('', $params, $urlParameters, $target);
6375  return $this->lastTypoLinkUrl;
6376  }
6377 
6385  public function typolinkWrap($conf)
6386  {
6387  $k = md5(microtime());
6388  return explode($k, $this->typoLink($k, $conf));
6389  }
6390 
6399  public function currentPageUrl($urlParameters = [], $id = 0)
6400  {
6401  $tsfe = $this->getTypoScriptFrontendController();
6402  return $this->getTypoLink_URL($id ?: $tsfe->id, $urlParameters, $tsfe->sPre);
6403  }
6404 
6414  protected function processUrl($context, $url, $typolinkConfiguration = [])
6415  {
6416  if (
6417  empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['urlProcessing']['urlProcessors'])
6418  || !is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['urlProcessing']['urlProcessors'])
6419  ) {
6420  return $url;
6421  }
6422 
6423  $urlProcessors = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['urlProcessing']['urlProcessors'];
6424  foreach ($urlProcessors as $identifier => $configuration) {
6425  if (empty($configuration) || !is_array($configuration)) {
6426  throw new \RuntimeException('Missing configuration for URI processor "' . $identifier . '".', 1442050529);
6427  }
6428  if (!is_string($configuration['processor']) || empty($configuration['processor']) || !class_exists($configuration['processor']) || !is_subclass_of($configuration['processor'], UrlProcessorInterface::class)) {
6429  throw new \RuntimeException('The URI processor "' . $identifier . '" defines an invalid provider. Ensure the class exists and implements the "' . UrlProcessorInterface::class . '".', 1442050579);
6430  }
6431  }
6432 
6433  $orderedProcessors = GeneralUtility::makeInstance(DependencyOrderingService::class)->orderByDependencies($urlProcessors);
6434  $keepProcessing = true;
6435 
6436  foreach ($orderedProcessors as $configuration) {
6438  $urlProcessor = GeneralUtility::makeInstance($configuration['processor']);
6439  $url = $urlProcessor->process($context, $url, $typolinkConfiguration, $this, $keepProcessing);
6440  if (!$keepProcessing) {
6441  break;
6442  }
6443  }
6444 
6445  return $url;
6446  }
6447 
6457  public function getClosestMPvalueForPage($pageId, $raw = false)
6458  {
6459  $tsfe = $this->getTypoScriptFrontendController();
6460  if (empty($GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) || !$tsfe->MP) {
6461  return '';
6462  }
6463  // MountPoints:
6464  $MP = '';
6465  // Same page as current.
6466  if ((int)$tsfe->id === (int)$pageId) {
6467  $MP = $tsfe->MP;
6468  } else {
6469  // ... otherwise find closest meeting point:
6470  // Gets rootline of linked-to page
6471  $tCR_rootline = $tsfe->sys_page->getRootLine($pageId, '', true);
6472  $inverseTmplRootline = array_reverse($tsfe->tmpl->rootLine);
6473  $rl_mpArray = [];
6474  $startMPaccu = false;
6475  // Traverse root line of link uid and inside of that the REAL root line of current position.
6476  foreach ($tCR_rootline as $tCR_data) {
6477  foreach ($inverseTmplRootline as $rlKey => $invTmplRLRec) {
6478  // Force accumulating when in overlay mode: Links to this page have to stay within the current branch
6479  if ($invTmplRLRec['_MOUNT_OL'] && (int)$tCR_data['uid'] === (int)$invTmplRLRec['uid']) {
6480  $startMPaccu = true;
6481  }
6482  // Accumulate MP data:
6483  if ($startMPaccu && $invTmplRLRec['_MP_PARAM']) {
6484  $rl_mpArray[] = $invTmplRLRec['_MP_PARAM'];
6485  }
6486  // If two PIDs matches and this is NOT the site root, start accumulation of MP data (on the next level):
6487  // (The check for site root is done so links to branches outsite the site but sharing the site roots PID
6488  // is NOT detected as within the branch!)
6489  if ((int)$tCR_data['pid'] === (int)$invTmplRLRec['pid'] && count($inverseTmplRootline) !== $rlKey + 1) {
6490  $startMPaccu = true;
6491  }
6492  }
6493  if ($startMPaccu) {
6494  // Good enough...
6495  break;
6496  }
6497  }
6498  if (!empty($rl_mpArray)) {
6499  $MP = implode(',', array_reverse($rl_mpArray));
6500  }
6501  }
6502  return $raw ? $MP : ($MP ? '&MP=' . rawurlencode($MP) : '');
6503  }
6504 
6514  public function getMailTo($mailAddress, $linktxt)
6515  {
6516  $mailAddress = (string)$mailAddress;
6517  if ((string)$linktxt === '') {
6518  $linktxt = htmlspecialchars($mailAddress);
6519  }
6520 
6521  $originalMailToUrl = 'mailto:' . $mailAddress;
6522  $mailToUrl = $this->processUrl(UrlProcessorInterface::CONTEXT_MAIL, $originalMailToUrl);
6523 
6524  $tsfe = $this->getTypoScriptFrontendController();
6525  // no processing happened, therefore
6526  if ($mailToUrl === $originalMailToUrl) {
6527  if ($tsfe->spamProtectEmailAddresses) {
6528  if ($tsfe->spamProtectEmailAddresses === 'ascii') {
6529  $mailToUrl = $tsfe->encryptEmail($mailToUrl);
6530  } else {
6531  $mailToUrl = 'javascript:linkTo_UnCryptMailto(' . GeneralUtility::quoteJSvalue($tsfe->encryptEmail($mailToUrl)) . ');';
6532  }
6533  $atLabel = '';
6534  if ($tsfe->config['config']['spamProtectEmailAddresses_atSubst']) {
6535  $atLabel = trim($tsfe->config['config']['spamProtectEmailAddresses_atSubst']);
6536  }
6537  $spamProtectedMailAddress = str_replace('@', $atLabel ? $atLabel : '(at)', htmlspecialchars($mailAddress));
6538  if ($tsfe->config['config']['spamProtectEmailAddresses_lastDotSubst']) {
6539  $lastDotLabel = trim($tsfe->config['config']['spamProtectEmailAddresses_lastDotSubst']);
6540  $lastDotLabel = $lastDotLabel ? $lastDotLabel : '(dot)';
6541  $spamProtectedMailAddress = preg_replace('/\\.([^\\.]+)$/', $lastDotLabel . '$1', $spamProtectedMailAddress);
6542  }
6543  $linktxt = str_ireplace($mailAddress, $spamProtectedMailAddress, $linktxt);
6544  }
6545  }
6546 
6547  return [$mailToUrl, $linktxt];
6548  }
6549 
6559  public function getQueryArguments($conf, $overruleQueryArguments = [], $forceOverruleArguments = false)
6560  {
6561  switch ((string)$conf['method']) {
6562  case 'GET':
6563  $currentQueryArray = GeneralUtility::_GET();
6564  break;
6565  case 'POST':
6566  $currentQueryArray = GeneralUtility::_POST();
6567  break;
6568  case 'GET,POST':
6569  $currentQueryArray = GeneralUtility::_GET();
6570  ArrayUtility::mergeRecursiveWithOverrule($currentQueryArray, GeneralUtility::_POST());
6571  break;
6572  case 'POST,GET':
6573  $currentQueryArray = GeneralUtility::_POST();
6574  ArrayUtility::mergeRecursiveWithOverrule($currentQueryArray, GeneralUtility::_GET());
6575  break;
6576  default:
6577  $currentQueryArray = GeneralUtility::explodeUrl2Array($this->getEnvironmentVariable('QUERY_STRING'), true);
6578  }
6579  if ($conf['exclude']) {
6580  $exclude = str_replace(',', '&', $conf['exclude']);
6581  $exclude = GeneralUtility::explodeUrl2Array($exclude, true);
6582  // never repeat id
6583  $exclude['id'] = 0;
6584  $newQueryArray = ArrayUtility::arrayDiffAssocRecursive($currentQueryArray, $exclude);
6585  } else {
6586  $newQueryArray = $currentQueryArray;
6587  }
6588  if ($forceOverruleArguments) {
6589  ArrayUtility::mergeRecursiveWithOverrule($newQueryArray, $overruleQueryArguments);
6590  } else {
6591  ArrayUtility::mergeRecursiveWithOverrule($newQueryArray, $overruleQueryArguments, false);
6592  }
6593  return GeneralUtility::implodeArrayForUrl('', $newQueryArray, '', false, true);
6594  }
6595 
6596  /***********************************************
6597  *
6598  * Miscellaneous functions, stand alone
6599  *
6600  ***********************************************/
6612  public function wrap($content, $wrap, $char = '|')
6613  {
6614  if ($wrap) {
6615  $wrapArr = explode($char, $wrap);
6616  $content = trim($wrapArr[0]) . $content . trim($wrapArr[1]);
6617  }
6618  return $content;
6619  }
6620 
6631  public function noTrimWrap($content, $wrap, $char = '|')
6632  {
6633  if ($wrap) {
6634  // expects to be wrapped with (at least) 3 characters (before, middle, after)
6635  // anything else is not taken into account
6636  $wrapArr = explode($char, $wrap, 4);
6637  $content = $wrapArr[1] . $content . $wrapArr[2];
6638  }
6639  return $content;
6640  }
6641 
6650  public function wrapSpace($content, $wrap, array $conf = null)
6651  {
6652  if (trim($wrap)) {
6653  $wrapArray = explode('|', $wrap);
6654  $wrapBefore = (int)$wrapArray[0];
6655  $wrapAfter = (int)$wrapArray[1];
6656  $useDivTag = isset($conf['useDiv']) && $conf['useDiv'];
6657  if ($wrapBefore) {
6658  if ($useDivTag) {
6659  $content = '<div class="content-spacer spacer-before" style="height:' . $wrapBefore . 'px;"></div>' . $content;
6660  } else {
6661  $content = '<span style="width: 1px; height: ' . $wrapBefore . 'px; display: inline-block;"></span><br />' . $content;
6662  }
6663  }
6664  if ($wrapAfter) {
6665  if ($useDivTag) {
6666  $content .= '<div class="content-spacer spacer-after" style="height:' . $wrapAfter . 'px;"></div>';
6667  } else {
6668  $content .= '<span style="width: 1px; height: ' . $wrapAfter . 'px; display: inline-block;"></span><br />';
6669  }
6670  }
6671  }
6672  return $content;
6673  }
6674 
6685  public function callUserFunction($funcName, $conf, $content)
6686  {
6687  // Split parts
6688  $parts = explode('->', $funcName);
6689  if (count($parts) === 2) {
6690  // Check whether PHP class is available
6691  if (class_exists($parts[0])) {
6692  $classObj = GeneralUtility::makeInstance($parts[0]);
6693  if (is_object($classObj) && method_exists($classObj, $parts[1])) {
6694  $classObj->cObj = $this;
6695  $content = call_user_func_array([
6696  $classObj,
6697  $parts[1]
6698  ], [
6699  $content,
6700  $conf
6701  ]);
6702  } else {
6703  $this->getTimeTracker()->setTSlogMessage('Method "' . $parts[1] . '" did not exist in class "' . $parts[0] . '"', 3);
6704  }
6705  } else {
6706  $this->getTimeTracker()->setTSlogMessage('Class "' . $parts[0] . '" did not exist', 3);
6707  }
6708  } elseif (function_exists($funcName)) {
6709  $content = call_user_func($funcName, $content, $conf);
6710  } else {
6711  $this->getTimeTracker()->setTSlogMessage('Function "' . $funcName . '" did not exist', 3);
6712  }
6713  return $content;
6714  }
6715 
6724  public function processParams($params)
6725  {
6726  GeneralUtility::logDeprecatedFunction();
6727  $paramArr = [];
6728  $lines = GeneralUtility::trimExplode(LF, $params, true);
6729  foreach ($lines as $val) {
6730  $pair = explode('=', $val, 2);
6731  $key = trim($pair[0]);
6732  if ($key[0] !== '#' && $key[0] !== '/') {
6733  $paramArr[$key] = trim($pair[1]);
6734  }
6735  }
6736  return $paramArr;
6737  }
6738 
6745  public function keywords($content)
6746  {
6747  $listArr = preg_split('/[,;' . LF . ']/', $content);
6748  foreach ($listArr as $k => $v) {
6749  $listArr[$k] = trim($v);
6750  }
6751  return implode(',', $listArr);
6752  }
6753 
6762  public function caseshift($theValue, $case)
6763  {
6764  $charsetConverter = GeneralUtility::makeInstance(CharsetConverter::class);
6765  switch (strtolower($case)) {
6766  case 'upper':
6767  $theValue = mb_strtoupper($theValue, 'utf-8');
6768  break;
6769  case 'lower':
6770  $theValue = mb_strtolower($theValue, 'utf-8');
6771  break;
6772  case 'capitalize':
6773  $theValue = mb_convert_case($theValue, MB_CASE_TITLE, 'utf-8');
6774  break;
6775  case 'ucfirst':
6776  $theValue = $charsetConverter->convCaseFirst('utf-8', $theValue, 'toUpper');
6777  break;
6778  case 'lcfirst':
6779  $theValue = $charsetConverter->convCaseFirst('utf-8', $theValue, 'toLower');
6780  break;
6781  case 'uppercamelcase':
6782  $theValue = GeneralUtility::underscoredToUpperCamelCase($theValue);
6783  break;
6784  case 'lowercamelcase':
6785  $theValue = GeneralUtility::underscoredToLowerCamelCase($theValue);
6786  break;
6787  }
6788  return $theValue;
6789  }
6790 
6799  public function HTMLcaseshift($theValue, $case)
6800  {
6801  $inside = 0;
6802  $newVal = '';
6803  $pointer = 0;
6804  $totalLen = strlen($theValue);
6805  do {
6806  if (!$inside) {
6807  $len = strcspn(substr($theValue, $pointer), '<');
6808  $newVal .= $this->caseshift(substr($theValue, $pointer, $len), $case);
6809  $inside = 1;
6810  } else {
6811  $len = strcspn(substr($theValue, $pointer), '>') + 1;
6812  $newVal .= substr($theValue, $pointer, $len);
6813  $inside = 0;
6814  }
6815  $pointer += $len;
6816  } while ($pointer < $totalLen);
6817  return $newVal;
6818  }
6819 
6827  public function calcAge($seconds, $labels)
6828  {
6829  if (MathUtility::canBeInterpretedAsInteger($labels)) {
6830  $labels = ' min| hrs| days| yrs| min| hour| day| year';
6831  } else {
6832  $labels = str_replace('"', '', $labels);
6833  }
6834  $labelArr = explode('|', $labels);
6835  if (count($labelArr) === 4) {
6836  $labelArr = array_merge($labelArr, $labelArr);
6837  }
6838  $absSeconds = abs($seconds);
6839  $sign = $seconds > 0 ? 1 : -1;
6840  if ($absSeconds < 3600) {
6841  $val = round($absSeconds / 60);
6842  $seconds = $sign * $val . ($val == 1 ? $labelArr[4] : $labelArr[0]);
6843  } elseif ($absSeconds < 24 * 3600) {
6844  $val = round($absSeconds / 3600);
6845  $seconds = $sign * $val . ($val == 1 ? $labelArr[5] : $labelArr[1]);
6846  } elseif ($absSeconds < 365 * 24 * 3600) {
6847  $val = round($absSeconds / (24 * 3600));
6848  $seconds = $sign * $val . ($val == 1 ? $labelArr[6] : $labelArr[2]);
6849  } else {
6850  $val = round($absSeconds / (365 * 24 * 3600));
6851  $seconds = $sign * $val . ($val == 1 ? $labelArr[7] : $labelArr[3]);
6852  }
6853  return $seconds;
6854  }
6855 
6867  public function sendNotifyEmail($message, $recipients, $cc, $senderAddress, $senderName = '', $replyTo = '')
6868  {
6870  $mail = GeneralUtility::makeInstance(MailMessage::class);
6871  $senderName = trim($senderName);
6872  $senderAddress = trim($senderAddress);
6873  if ($senderName !== '' && $senderAddress !== '') {
6874  $sender = [$senderAddress => $senderName];
6875  } elseif ($senderAddress !== '') {
6876  $sender = [$senderAddress];
6877  } else {
6878  $sender = MailUtility::getSystemFrom();
6879  }
6880  $mail->setFrom($sender);
6881  $parsedReplyTo = MailUtility::parseAddresses($replyTo);
6882  if (!empty($parsedReplyTo)) {
6883  $mail->setReplyTo($parsedReplyTo);
6884  }
6885  $message = trim($message);
6886  if ($message !== '') {
6887  // First line is subject
6888  $messageParts = explode(LF, $message, 2);
6889  $subject = trim($messageParts[0]);
6890  $plainMessage = trim($messageParts[1]);
6891  $parsedRecipients = MailUtility::parseAddresses($recipients);
6892  if (!empty($parsedRecipients)) {
6893  $mail->setTo($parsedRecipients)
6894  ->setSubject($subject)
6895  ->setBody($plainMessage);
6896  $mail->send();
6897  }
6898  $parsedCc = MailUtility::parseAddresses($cc);
6899  if (!empty($parsedCc)) {
6901  $mail = GeneralUtility::makeInstance(MailMessage::class);
6902  if (!empty($parsedReplyTo)) {
6903  $mail->setReplyTo($parsedReplyTo);
6904  }
6905  $mail->setFrom($sender)
6906  ->setTo($parsedCc)
6907  ->setSubject($subject)
6908  ->setBody($plainMessage);
6909  $mail->send();
6910  }
6911  return true;
6912  }
6913  return false;
6914  }
6915 
6923  public function URLqMark($url, $params)
6924  {
6925  if ($params && !strstr($url, '?')) {
6926  return $url . '?' . $params;
6927  } else {
6928  return $url . $params;
6929  }
6930  }
6931 
6940  public function clearTSProperties($TSArr, $propList)
6941  {
6942  $list = explode(',', $propList);
6943  foreach ($list as $prop) {
6944  $prop = trim($prop);
6945  unset($TSArr[$prop]);
6946  unset($TSArr[$prop . '.']);
6947  }
6948  return $TSArr;
6949  }
6950 
6959  public function mergeTSRef($confArr, $prop)
6960  {
6961  if ($confArr[$prop][0] === '<') {
6962  $key = trim(substr($confArr[$prop], 1));
6963  $cF = GeneralUtility::makeInstance(TypoScriptParser::class);
6964  // $name and $conf is loaded with the referenced values.
6965  $old_conf = $confArr[$prop . '.'];
6966  list(, $conf) = $cF->getVal($key, $this->getTypoScriptFrontendController()->tmpl->setup);
6967  if (is_array($old_conf) && !empty($old_conf)) {
6968  $conf = is_array($conf) ? array_replace_recursive($conf, $old_conf) : $old_conf;
6969  }
6970  $confArr[$prop . '.'] = $conf;
6971  }
6972  return $confArr;
6973  }
6974 
6983  public function gifBuilderTextBox($gifbuilderConf, $conf, $text)
6984  {
6985  $chars = (int)$conf['chars'] ?: 20;
6986  $lineDist = (int)$conf['lineDist'] ?: 20;
6987  $Valign = strtolower(trim($conf['Valign']));
6988  $tmplObjNumber = (int)$conf['tmplObjNumber'];
6989  $maxLines = (int)$conf['maxLines'];
6990  if ($tmplObjNumber && $gifbuilderConf[$tmplObjNumber] === 'TEXT') {
6991  $textArr = $this->linebreaks($text, $chars, $maxLines);
6992  $angle = (int)$gifbuilderConf[$tmplObjNumber . '.']['angle'];
6993  foreach ($textArr as $c => $textChunk) {
6994  $index = $tmplObjNumber + 1 + $c * 2;
6995  // Workarea
6996  $gifbuilderConf = $this->clearTSProperties($gifbuilderConf, $index);
6997  $rad_angle = 2 * pi() / 360 * $angle;
6998  $x_d = sin($rad_angle) * $lineDist;
6999  $y_d = cos($rad_angle) * $lineDist;
7000  $diff_x_d = 0;
7001  $diff_y_d = 0;
7002  if ($Valign === 'center') {
7003  $diff_x_d = $x_d * count($textArr);
7004  $diff_x_d = $diff_x_d / 2;
7005  $diff_y_d = $y_d * count($textArr);
7006  $diff_y_d = $diff_y_d / 2;
7007  }
7008  $x_d = round($x_d * $c - $diff_x_d);
7009  $y_d = round($y_d * $c - $diff_y_d);
7010  $gifbuilderConf[$index] = 'WORKAREA';
7011  $gifbuilderConf[$index . '.']['set'] = $x_d . ',' . $y_d;
7012  // Text
7013  $index++;
7014  $gifbuilderConf = $this->clearTSProperties($gifbuilderConf, $index);
7015  $gifbuilderConf[$index] = 'TEXT';
7016  $gifbuilderConf[$index . '.'] = $this->clearTSProperties($gifbuilderConf[$tmplObjNumber . '.'], 'text');
7017  $gifbuilderConf[$index . '.']['text'] = $textChunk;
7018  }
7019  $gifbuilderConf = $this->clearTSProperties($gifbuilderConf, $tmplObjNumber);
7020  }
7021  return $gifbuilderConf;
7022  }
7023 
7034  public function linebreaks($string, $chars, $maxLines = 0)
7035  {
7036  $lines = explode(LF, $string);
7037  $lineArr = [];
7038  $c = 0;
7039  foreach ($lines as $paragraph) {
7040  $words = explode(' ', $paragraph);
7041  foreach ($words as $word) {
7042  if (strlen($lineArr[$c] . $word) > $chars) {
7043  $c++;
7044  }
7045  if (!$maxLines || $c < $maxLines) {
7046  $lineArr[$c] .= $word . ' ';
7047  }
7048  }
7049  $c++;
7050  }
7051  return $lineArr;
7052  }
7053 
7061  public function includeLibs(array $config)
7062  {
7063  GeneralUtility::logDeprecatedFunction();
7064  $librariesIncluded = false;
7065  if (isset($config['includeLibs']) && $config['includeLibs']) {
7066  $libraries = GeneralUtility::trimExplode(',', $config['includeLibs'], true);
7067  $this->getTypoScriptFrontendController()->includeLibraries($libraries);
7068  $librariesIncluded = true;
7069  }
7070  return $librariesIncluded;
7071  }
7072 
7073  /***********************************************
7074  *
7075  * Database functions, making of queries
7076  *
7077  ***********************************************/
7078 
7094  public function enableFields($table, $show_hidden = false, array $ignore_array = [])
7095  {
7096  $tsfe = $this->getTypoScriptFrontendController();
7097  $show_hidden = $show_hidden ?: ($table === 'pages' ? $tsfe->showHiddenPage : $tsfe->showHiddenRecords);
7098  return $tsfe->sys_page->enableFields($table, (bool)$show_hidden, $ignore_array);
7099  }
7100 
7127  public function getTreeList($id, $depth, $begin = 0, $dontCheckEnableFields = false, $addSelectFields = '', $moreWhereClauses = '', array $prevId_array = [], $recursionLevel = 0)
7128  {
7129  $id = (int)$id;
7130  if (!$id) {
7131  return '';
7132  }
7133 
7134  // Init vars:
7135  $allFields = 'uid,hidden,starttime,endtime,fe_group,extendToSubpages,doktype,php_tree_stop,mount_pid,mount_pid_ol,t3ver_state' . $addSelectFields;
7136  $depth = (int)$depth;
7137  $begin = (int)$begin;
7138  $theList = [];
7139  $addId = 0;
7140  $requestHash = '';
7141 
7142  // First level, check id (second level, this is done BEFORE the recursive call)
7143  $tsfe = $this->getTypoScriptFrontendController();
7144  if (!$recursionLevel) {
7145  // Check tree list cache
7146  // First, create the hash for this request - not sure yet whether we need all these parameters though
7147  $parameters = [
7148  $id,
7149  $depth,
7150  $begin,
7151  $dontCheckEnableFields,
7152  $addSelectFields,
7153  $moreWhereClauses,
7154  $prevId_array,
7155  $tsfe->gr_list
7156  ];
7157  $requestHash = md5(serialize($parameters));
7158  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
7159  ->getQueryBuilderForTable('cache_treelist');
7160  $cacheEntry = $queryBuilder->select('treelist')
7161  ->from('cache_treelist')
7162  ->where(
7163  $queryBuilder->expr()->eq(
7164  'md5hash',
7165  $queryBuilder->createNamedParameter($requestHash, \PDO::PARAM_STR)
7166  ),
7167  $queryBuilder->expr()->orX(
7168  $queryBuilder->expr()->gt(
7169  'expires',
7170  $queryBuilder->createNamedParameter($GLOBALS['EXEC_TIME'], \PDO::PARAM_INT)
7171  ),
7172  $queryBuilder->expr()->eq('expires', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
7173  )
7174  )
7175  ->setMaxResults(1)
7176  ->execute()
7177  ->fetch();
7178 
7179  if (is_array($cacheEntry)) {
7180  // Cache hit
7181  return $cacheEntry['treelist'];
7182  }
7183  // If Id less than zero it means we should add the real id to list:
7184  if ($id < 0) {
7185  $addId = $id = abs($id);
7186  }
7187  // Check start page:
7188  if ($tsfe->sys_page->getRawRecord('pages', $id, 'uid')) {
7189  // Find mount point if any:
7190  $mount_info = $tsfe->sys_page->getMountPointInfo($id);
7191  if (is_array($mount_info)) {
7192  $id = $mount_info['mount_pid'];
7193  // In Overlay mode, use the mounted page uid as added ID!:
7194  if ($addId && $mount_info['overlay']) {
7195  $addId = $id;
7196  }
7197  }
7198  } else {
7199  // Return blank if the start page was NOT found at all!
7200  return '';
7201  }
7202  }
7203  // Add this ID to the array of IDs
7204  if ($begin <= 0) {
7205  $prevId_array[] = $id;
7206  }
7207  // Select sublevel:
7208  if ($depth > 0) {
7209  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
7210  $queryBuilder->getRestrictions()
7211  ->removeAll()
7212  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
7213  $queryBuilder->select(...GeneralUtility::trimExplode(',', $allFields, true))
7214  ->from('pages')
7215  ->where(
7216  $queryBuilder->expr()->eq(
7217  'pid',
7218  $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
7219  )
7220  )
7221  ->orderBy('sorting');
7222 
7223  if (!empty($moreWhereClauses)) {
7224  $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($moreWhereClauses));
7225  }
7226 
7227  $result = $queryBuilder->execute();
7228  while ($row = $result->fetch()) {
7230  $versionState = VersionState::cast($row['t3ver_state']);
7231  $tsfe->sys_page->versionOL('pages', $row);
7232  if ((int)$row['doktype'] === PageRepository::DOKTYPE_RECYCLER
7233  || (int)$row['doktype'] === PageRepository::DOKTYPE_BE_USER_SECTION
7234  || $versionState->indicatesPlaceholder()
7235  ) {
7236  // Doing this after the overlay to make sure changes
7237  // in the overlay are respected.
7238  // However, we do not process pages below of and
7239  // including of type recycler and BE user section
7240  continue;
7241  }
7242  // Find mount point if any:
7243  $next_id = $row['uid'];
7244  $mount_info = $tsfe->sys_page->getMountPointInfo($next_id, $row);
7245  // Overlay mode:
7246  if (is_array($mount_info) && $mount_info['overlay']) {
7247  $next_id = $mount_info['mount_pid'];
7248  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
7249  ->getQueryBuilderForTable('pages');
7250  $queryBuilder->getRestrictions()
7251  ->removeAll()
7252  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
7253  $queryBuilder->select(...GeneralUtility::trimExplode(',', $allFields, true))
7254  ->from('pages')
7255  ->where(
7256  $queryBuilder->expr()->eq(
7257  'uid',
7258  $queryBuilder->createNamedParameter($next_id, \PDO::PARAM_INT)
7259  )
7260  )
7261  ->orderBy('sorting')
7262  ->setMaxResults(1);
7263 
7264  if (!empty($moreWhereClauses)) {
7265  $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($moreWhereClauses));
7266  }
7267 
7268  $row = $queryBuilder->execute()->fetch();
7269  $tsfe->sys_page->versionOL('pages', $row);
7270  if ((int)$row['doktype'] === PageRepository::DOKTYPE_RECYCLER
7271  || (int)$row['doktype'] === PageRepository::DOKTYPE_BE_USER_SECTION
7272  || $versionState->indicatesPlaceholder()
7273  ) {
7274  // Doing this after the overlay to make sure
7275  // changes in the overlay are respected.
7276  // see above
7277  continue;
7278  }
7279  }
7280  // Add record:
7281  if ($dontCheckEnableFields || $tsfe->checkPagerecordForIncludeSection($row)) {
7282  // Add ID to list:
7283  if ($begin <= 0) {
7284  if ($dontCheckEnableFields || $tsfe->checkEnableFields($row)) {
7285  $theList[] = $next_id;
7286  }
7287  }
7288  // Next level:
7289  if ($depth > 1 && !$row['php_tree_stop']) {
7290  // Normal mode:
7291  if (is_array($mount_info) && !$mount_info['overlay']) {
7292  $next_id = $mount_info['mount_pid'];
7293  }
7294  // Call recursively, if the id is not in prevID_array:
7295  if (!in_array($next_id, $prevId_array)) {
7296  $theList = array_merge(
7297  GeneralUtility::intExplode(
7298  ',',
7299  $this->getTreeList($next_id, $depth - 1, $begin - 1,
7300  $dontCheckEnableFields, $addSelectFields, $moreWhereClauses,
7301  $prevId_array, $recursionLevel + 1),
7302  true
7303  ),
7304  $theList
7305  );
7306  }
7307  }
7308  }
7309  }
7310  }
7311  // If first run, check if the ID should be returned:
7312  if (!$recursionLevel) {
7313  if ($addId) {
7314  if ($begin > 0) {
7315  $theList[] = 0;
7316  } else {
7317  $theList[] = $addId;
7318  }
7319  }
7320  GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('cache_treelist')->insert(
7321  'cache_treelist',
7322  [
7323  'md5hash' => $requestHash,
7324  'pid' => $id,
7325  'treelist' => implode(',', $theList),
7326  'tstamp' => $GLOBALS['EXEC_TIME']
7327  ]
7328  );
7329  }
7330 
7331  return implode(',', $theList);
7332  }
7333 
7343  public function searchWhere($searchWords, $searchFieldList, $searchTable = '')
7344  {
7345  if (!$searchWords) {
7346  return ' AND 1=1';
7347  }
7348 
7349  if (empty($searchTable)) {
7350  GeneralUtility::deprecationLog(
7351  'Parameter 3 of ContentObjectRenderer::searchWhere() is required can not be omitted anymore. Using Default connection!'
7352  );
7353  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
7354  ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME)
7355  ->createQueryBuilder();
7356  } else {
7357  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
7358  ->getQueryBuilderForTable($searchTable);
7359  }
7360 
7361  $prefixTableName = $searchTable ? $searchTable . '.' : '';
7362 
7363  $where = $queryBuilder->expr()->andX();
7364  $searchFields = explode(',', $searchFieldList);
7365  $searchWords = preg_split('/[ ,]/', $searchWords);
7366  foreach ($searchWords as $searchWord) {
7367  $searchWord = trim($searchWord);
7368  if (strlen($searchWord) < 3) {
7369  continue;
7370  }
7371  $searchWordConstraint = $queryBuilder->expr()->orX();
7372  $searchWord = $queryBuilder->escapeLikeWildcards($searchWord);
7373  foreach ($searchFields as $field) {
7374  $searchWordConstraint->add(
7375  $queryBuilder->expr()->like(
7376  $prefixTableName . $field,
7377  $queryBuilder->createNamedParameter('%' . $searchWord . '%', \PDO::PARAM_STR)
7378  )
7379  );
7380  }
7381 
7382  if ($searchWordConstraint->count()) {
7383  $where->add($searchWordConstraint);
7384  }
7385  }
7386 
7387  return ' AND ' . (string)$where;
7388  }
7389 
7399  public function exec_getQuery($table, $conf)
7400  {
7401  $statement = $this->getQuery($table, $conf);
7402  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
7403 
7404  return $connection->executeQuery($statement);
7405  }
7406 
7416  public function getRecords($tableName, array $queryConfiguration)
7417  {
7418  $records = [];
7419 
7420  $statement = $this->exec_getQuery($tableName, $queryConfiguration);
7421 
7422  $tsfe = $this->getTypoScriptFrontendController();
7423  while ($row = $statement->fetch()) {
7424  // Versioning preview:
7425  $tsfe->sys_page->versionOL($tableName, $row, true);
7426 
7427  // Language overlay:
7428  if (is_array($row) && $tsfe->sys_language_contentOL) {
7429  if ($tableName === 'pages') {
7430  $row = $tsfe->sys_page->getPageOverlay($row);
7431  } else {
7432  $row = $tsfe->sys_page->getRecordOverlay(
7433  $tableName,
7434  $row,
7435  $tsfe->sys_language_content,
7436  $tsfe->sys_language_contentOL
7437  );
7438  }
7439  }
7440 
7441  // Might be unset in the sys_language_contentOL
7442  if (is_array($row)) {
7443  $records[] = $row;
7444  }
7445  }
7446 
7447  return $records;
7448  }
7449 
7463  public function getQuery($table, $conf, $returnQueryArray = false)
7464  {
7465  // Resolve stdWrap in these properties first
7466  $properties = [
7467  'pidInList',
7468  'uidInList',
7469  'languageField',
7470  'selectFields',
7471  'max',
7472  'begin',
7473  'groupBy',
7474  'orderBy',
7475  'join',
7476  'leftjoin',
7477  'rightjoin',
7478  'recursive',
7479  'where'
7480  ];
7481  foreach ($properties as $property) {
7482  $conf[$property] = trim(isset($conf[$property . '.'])
7483  ? $this->stdWrap($conf[$property], $conf[$property . '.'])
7484  : $conf[$property]
7485  );
7486  if ($conf[$property] === '') {
7487  unset($conf[$property]);
7488  }
7489  if (isset($conf[$property . '.'])) {
7490  // stdWrapping already done, so remove the sub-array
7491  unset($conf[$property . '.']);
7492  }
7493  }
7494  // Handle PDO-style named parameter markers first
7495  $queryMarkers = $this->getQueryMarkers($table, $conf);
7496  // Replace the markers in the non-stdWrap properties
7497  foreach ($queryMarkers as $marker => $markerValue) {
7498  $properties = [
7499  'uidInList',
7500  'selectFields',
7501  'where',
7502  'max',
7503  'begin',
7504  'groupBy',
7505  'orderBy',
7506  'join',
7507  'leftjoin',
7508  'rightjoin'
7509  ];
7510  foreach ($properties as $property) {
7511  if ($conf[$property]) {
7512  $conf[$property] = str_replace('###' . $marker . '###', $markerValue, $conf[$property]);
7513  }
7514  }
7515  }
7516 
7517  // Construct WHERE clause:
7518  // Handle recursive function for the pidInList
7519  if (isset($conf['recursive'])) {
7520  $conf['recursive'] = (int)$conf['recursive'];
7521  if ($conf['recursive'] > 0) {
7522  $pidList = GeneralUtility::trimExplode(',', $conf['pidInList'], true);
7523  array_walk($pidList, function (&$storagePid) {
7524  if ($storagePid === 'this') {
7525  $storagePid = $this->getTypoScriptFrontendController()->id;
7526  }
7527  if ($storagePid > 0) {
7528  $storagePid = -$storagePid;
7529  }
7530  });
7531  $expandedPidList = [];
7532  foreach ($pidList as $value) {
7533  // Implementation of getTreeList allows to pass the id negative to include
7534  // it into the result otherwise only childpages are returned
7535  $expandedPidList = array_merge(
7536  GeneralUtility::intExplode(',', $this->getTreeList($value, $conf['recursive'])),
7537  $expandedPidList
7538  );
7539  }
7540  $conf['pidInList'] = implode(',', $expandedPidList);
7541  }
7542  }
7543  if ((string)$conf['pidInList'] === '') {
7544  $conf['pidInList'] = 'this';
7545  }
7546 
7547  $queryParts = $this->getQueryConstraints($table, $conf);
7548 
7549  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
7550  // @todo Check against getQueryConstraints, can probably use FrontendRestrictions
7551  // @todo here and remove enableFields there.
7552  $queryBuilder->getRestrictions()->removeAll();
7553  $queryBuilder->select('*')->from($table);
7554 
7555  if ($queryParts['where']) {
7556  $queryBuilder->where($queryParts['where']);
7557  }
7558 
7559  if ($queryParts['groupBy']) {
7560  $queryBuilder->groupBy(...$queryParts['groupBy']);
7561  }
7562 
7563  if (is_array($queryParts['orderBy'])) {
7564  foreach ($queryParts['orderBy'] as $orderBy) {
7565  $queryBuilder->addOrderBy(...$orderBy);
7566  }
7567  }
7568 
7569  // Fields:
7570  if ($conf['selectFields']) {
7571  $queryBuilder->selectLiteral($this->sanitizeSelectPart($conf['selectFields'], $table));
7572  }
7573 
7574  // Setting LIMIT:
7575  $error = false;
7576  if ($conf['max'] || $conf['begin']) {
7577  // Finding the total number of records, if used:
7578  if (strpos(strtolower($conf['begin'] . $conf['max']), 'total') !== false) {
7579  $countQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
7580  $countQueryBuilder->getRestrictions()->removeAll();
7581  $countQueryBuilder->count('*')
7582  ->from($table)
7583  ->where($queryParts['where']);
7584 
7585  if ($queryParts['groupBy']) {
7586  $countQueryBuilder->groupBy(...$queryParts['groupBy']);
7587  }
7588 
7589  try {
7590  $count = $countQueryBuilder->execute()->fetchColumn(0);
7591  $conf['max'] = str_ireplace('total', $count, $conf['max']);
7592  $conf['begin'] = str_ireplace('total', $count, $conf['begin']);
7593  } catch (DBALException $e) {
7594  $this->getTimeTracker()->setTSlogMessage($e->getPrevious()->getMessage());
7595  $error = true;
7596  }
7597  }
7598 
7599  if (!$error) {
7600  $conf['begin'] = MathUtility::forceIntegerInRange(ceil($this->calc($conf['begin'])), 0);
7601  $conf['max'] = MathUtility::forceIntegerInRange(ceil($this->calc($conf['max'])), 0);
7602  if ($conf['begin'] > 0) {
7603  $queryBuilder->setFirstResult($conf['begin']);
7604  }
7605  $queryBuilder->setMaxResults($conf['max'] ?: 100000);
7606  }
7607  }
7608 
7609  if (!$error) {
7610  // Setting up tablejoins:
7611  if ($conf['join']) {
7612  $joinParts = QueryHelper::parseJoin($conf['join']);
7613  $queryBuilder->join(
7614  $table,
7615  $joinParts['tableName'],
7616  $joinParts['tableAlias'],
7617  $joinParts['joinCondition']
7618  );
7619  } elseif ($conf['leftjoin']) {
7620  $joinParts = QueryHelper::parseJoin($conf['leftjoin']);
7621  $queryBuilder->leftJoin(
7622  $table,
7623  $joinParts['tableName'],
7624  $joinParts['tableAlias'],
7625  $joinParts['joinCondition']
7626  );
7627  } elseif ($conf['rightjoin']) {
7628  $joinParts = QueryHelper::parseJoin($conf['rightjoin']);
7629  $queryBuilder->rightJoin(