TYPO3 CMS  TYPO3_8-7
ContentObjectRenderer.php
Go to the documentation of this file.
1 <?php
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 
65 
76 {
80  public $align = [
81  'center',
82  'right',
83  'left'
84  ];
85 
91  public $stdWrapOrder = [
92  'stdWrapPreProcess' => 'hook',
93  // this is a placeholder for the first Hook
94  'cacheRead' => 'hook',
95  // this is a placeholder for checking if the content is available in cache
96  'setContentToCurrent' => 'boolean',
97  'setContentToCurrent.' => 'array',
98  'addPageCacheTags' => 'string',
99  'addPageCacheTags.' => 'array',
100  'setCurrent' => 'string',
101  'setCurrent.' => 'array',
102  'lang.' => 'array',
103  'data' => 'getText',
104  'data.' => 'array',
105  'field' => 'fieldName',
106  'field.' => 'array',
107  'current' => 'boolean',
108  'current.' => 'array',
109  'cObject' => 'cObject',
110  'cObject.' => 'array',
111  'numRows.' => 'array',
112  'filelist' => 'dir',
113  'filelist.' => 'array',
114  'preUserFunc' => 'functionName',
115  'stdWrapOverride' => 'hook',
116  // this is a placeholder for the second Hook
117  'override' => 'string',
118  'override.' => 'array',
119  'preIfEmptyListNum' => 'listNum',
120  'preIfEmptyListNum.' => 'array',
121  'ifNull' => 'string',
122  'ifNull.' => 'array',
123  'ifEmpty' => 'string',
124  'ifEmpty.' => 'array',
125  'ifBlank' => 'string',
126  'ifBlank.' => 'array',
127  'listNum' => 'listNum',
128  'listNum.' => 'array',
129  'trim' => 'boolean',
130  'trim.' => 'array',
131  'strPad.' => 'array',
132  'stdWrap' => 'stdWrap',
133  'stdWrap.' => 'array',
134  'stdWrapProcess' => 'hook',
135  // this is a placeholder for the third Hook
136  'required' => 'boolean',
137  'required.' => 'array',
138  'if.' => 'array',
139  'fieldRequired' => 'fieldName',
140  'fieldRequired.' => 'array',
141  'csConv' => 'string',
142  'csConv.' => 'array',
143  'parseFunc' => 'objectpath',
144  'parseFunc.' => 'array',
145  'HTMLparser' => 'boolean',
146  'HTMLparser.' => 'array',
147  'split.' => 'array',
148  'replacement.' => 'array',
149  'prioriCalc' => 'boolean',
150  'prioriCalc.' => 'array',
151  'char' => 'integer',
152  'char.' => 'array',
153  'intval' => 'boolean',
154  'intval.' => 'array',
155  'hash' => 'string',
156  'hash.' => 'array',
157  'round' => 'boolean',
158  'round.' => 'array',
159  'numberFormat.' => 'array',
160  'expandList' => 'boolean',
161  'expandList.' => 'array',
162  'date' => 'dateconf',
163  'date.' => 'array',
164  'strtotime' => 'strtotimeconf',
165  'strtotime.' => 'array',
166  'strftime' => 'strftimeconf',
167  'strftime.' => 'array',
168  'age' => 'boolean',
169  'age.' => 'array',
170  'case' => 'case',
171  'case.' => 'array',
172  'bytes' => 'boolean',
173  'bytes.' => 'array',
174  'substring' => 'parameters',
175  'substring.' => 'array',
176  'removeBadHTML' => 'boolean',
177  'removeBadHTML.' => 'array',
178  'cropHTML' => 'crop',
179  'cropHTML.' => 'array',
180  'stripHtml' => 'boolean',
181  'stripHtml.' => 'array',
182  'crop' => 'crop',
183  'crop.' => 'array',
184  'rawUrlEncode' => 'boolean',
185  'rawUrlEncode.' => 'array',
186  'htmlSpecialChars' => 'boolean',
187  'htmlSpecialChars.' => 'array',
188  'encodeForJavaScriptValue' => 'boolean',
189  'encodeForJavaScriptValue.' => 'array',
190  'doubleBrTag' => 'string',
191  'doubleBrTag.' => 'array',
192  'br' => 'boolean',
193  'br.' => 'array',
194  'brTag' => 'string',
195  'brTag.' => 'array',
196  'encapsLines.' => 'array',
197  'keywords' => 'boolean',
198  'keywords.' => 'array',
199  'innerWrap' => 'wrap',
200  'innerWrap.' => 'array',
201  'innerWrap2' => 'wrap',
202  'innerWrap2.' => 'array',
203  'fontTag' => 'wrap',
204  'fontTag.' => 'array',
205  'addParams.' => 'array',
206  'filelink.' => 'array',
207  'preCObject' => 'cObject',
208  'preCObject.' => 'array',
209  'postCObject' => 'cObject',
210  'postCObject.' => 'array',
211  'wrapAlign' => 'align',
212  'wrapAlign.' => 'array',
213  'typolink.' => 'array',
214  'TCAselectItem.' => 'array',
215  'space' => 'space',
216  'space.' => 'array',
217  'spaceBefore' => 'int',
218  'spaceBefore.' => 'array',
219  'spaceAfter' => 'int',
220  'spaceAfter.' => 'array',
221  'wrap' => 'wrap',
222  'wrap.' => 'array',
223  'noTrimWrap' => 'wrap',
224  'noTrimWrap.' => 'array',
225  'wrap2' => 'wrap',
226  'wrap2.' => 'array',
227  'dataWrap' => 'dataWrap',
228  'dataWrap.' => 'array',
229  'prepend' => 'cObject',
230  'prepend.' => 'array',
231  'append' => 'cObject',
232  'append.' => 'array',
233  'wrap3' => 'wrap',
234  'wrap3.' => 'array',
235  'orderedStdWrap' => 'stdWrap',
236  'orderedStdWrap.' => 'array',
237  'outerWrap' => 'wrap',
238  'outerWrap.' => 'array',
239  'insertData' => 'boolean',
240  'insertData.' => 'array',
241  'postUserFunc' => 'functionName',
242  'postUserFuncInt' => 'functionName',
243  'prefixComment' => 'string',
244  'prefixComment.' => 'array',
245  'editIcons' => 'string',
246  'editIcons.' => 'array',
247  'editPanel' => 'boolean',
248  'editPanel.' => 'array',
249  'cacheStore' => 'hook',
250  // this is a placeholder for storing the content in cache
251  'stdWrapPostProcess' => 'hook',
252  // this is a placeholder for the last Hook
253  'debug' => 'boolean',
254  'debug.' => 'array',
255  'debugFunc' => 'boolean',
256  'debugFunc.' => 'array',
257  'debugData' => 'boolean',
258  'debugData.' => 'array'
259  ];
260 
266  protected $contentObjectClassMap = [];
267 
277  public $data = [];
278 
282  protected $table = '';
283 
289  public $oldData = [];
290 
296  public $alternativeData = '';
297 
303  public $parameters = [];
304 
308  public $currentValKey = 'currentValue_kidjls9dksoje';
309 
316  public $currentRecord = '';
317 
324 
331 
338 
344  public $parentRecord = [];
345 
351  public $checkPid_cache = [];
352 
356  public $checkPid_badDoktypeList = '255';
357 
363  public $lastTypoLinkUrl = '';
364 
370  public $lastTypoLinkTarget = '';
371 
375  public $lastTypoLinkLD = [];
376 
382  public $recordRegister = [];
383 
389  protected $cObjHookObjectsRegistry = [];
390 
394  public $cObjHookObjectsArr = [];
395 
401  protected $stdWrapHookObjects = [];
402 
409 
413  protected $currentFile = null;
414 
419 
425  protected $userObjectType = false;
426 
430  protected $stopRendering = [];
431 
435  protected $stdWrapRecursionLevel = 0;
436 
441 
445  protected $templateService;
446 
458  const OBJECTTYPE_USER = 2;
459 
464  {
465  $this->typoScriptFrontendController = $typoScriptFrontendController;
466  $this->contentObjectClassMap = $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'];
467  $this->templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
468  }
469 
477  public function __sleep()
478  {
479  $vars = get_object_vars($this);
480  unset($vars['typoScriptFrontendController']);
481  if ($this->currentFile instanceof FileReference) {
482  $this->currentFile = 'FileReference:' . $this->currentFile->getUid();
483  } elseif ($this->currentFile instanceof File) {
484  $this->currentFile = 'File:' . $this->currentFile->getIdentifier();
485  } else {
486  unset($vars['currentFile']);
487  }
488  return array_keys($vars);
489  }
490 
496  public function __wakeup()
497  {
498  if (isset($GLOBALS['TSFE'])) {
499  $this->typoScriptFrontendController = $GLOBALS['TSFE'];
500  }
501  if ($this->currentFile !== null && is_string($this->currentFile)) {
502  list($objectType, $identifier) = explode(':', $this->currentFile, 2);
503  try {
504  if ($objectType === 'File') {
505  $this->currentFile = ResourceFactory::getInstance()->retrieveFileOrFolderObject($identifier);
506  } elseif ($objectType === 'FileReference') {
507  $this->currentFile = ResourceFactory::getInstance()->getFileReferenceObject($identifier);
508  }
509  } catch (ResourceDoesNotExistException $e) {
510  $this->currentFile = null;
511  }
512  }
513  }
514 
525  {
526  $this->contentObjectClassMap = $contentObjectClassMap;
527  }
528 
539  public function registerContentObjectClass($className, $contentObjectName)
540  {
541  $this->contentObjectClassMap[$contentObjectName] = $className;
542  }
543 
552  public function start($data, $table = '')
553  {
554  $this->data = $data;
555  $this->table = $table;
556  $this->currentRecord = $table !== '' ? $table . ':' . $this->data['uid'] : '';
557  $this->parameters = [];
558  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['cObjTypeAndClass'])) {
559  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['cObjTypeAndClass'] as $classArr) {
560  $this->cObjHookObjectsRegistry[$classArr[0]] = $classArr[1];
561  }
562  }
563  $this->stdWrapHookObjects = [];
564  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap'])) {
565  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap'] as $classData) {
566  $hookObject = GeneralUtility::getUserObj($classData);
567  if (!$hookObject instanceof ContentObjectStdWrapHookInterface) {
568  throw new \UnexpectedValueException($classData . ' must implement interface ' . ContentObjectStdWrapHookInterface::class, 1195043965);
569  }
570  $this->stdWrapHookObjects[] = $hookObject;
571  }
572  }
573  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['postInit'])) {
574  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['postInit'] as $classData) {
575  $postInitializationProcessor = GeneralUtility::getUserObj($classData);
576  if (!$postInitializationProcessor instanceof ContentObjectPostInitHookInterface) {
577  throw new \UnexpectedValueException($classData . ' must implement interface ' . ContentObjectPostInitHookInterface::class, 1274563549);
578  }
579  $postInitializationProcessor->postProcessContentObjectInitialization($this);
580  }
581  }
582  }
583 
589  public function getCurrentTable()
590  {
591  return $this->table;
592  }
593 
600  protected function getGetImgResourceHookObjects()
601  {
602  if (!isset($this->getImgResourceHookObjects)) {
603  $this->getImgResourceHookObjects = [];
604  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImgResource'])) {
605  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImgResource'] as $classData) {
606  $hookObject = GeneralUtility::getUserObj($classData);
607  if (!$hookObject instanceof ContentObjectGetImageResourceHookInterface) {
608  throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetImageResourceHookInterface::class, 1218636383);
609  }
610  $this->getImgResourceHookObjects[] = $hookObject;
611  }
612  }
613  }
615  }
616 
625  public function setParent($data, $currentRecord)
626  {
627  $this->parentRecord = [
628  'data' => $data,
629  'currentRecord' => $currentRecord
630  ];
631  }
632 
633  /***********************************************
634  *
635  * CONTENT_OBJ:
636  *
637  ***********************************************/
646  public function getCurrentVal()
647  {
648  return $this->data[$this->currentValKey];
649  }
650 
657  public function setCurrentVal($value)
658  {
659  $this->data[$this->currentValKey] = $value;
660  }
661 
671  public function cObjGet($setup, $addKey = '')
672  {
673  if (!is_array($setup)) {
674  return '';
675  }
676  $sKeyArray = ArrayUtility::filterAndSortByNumericKeys($setup);
677  $content = '';
678  foreach ($sKeyArray as $theKey) {
679  $theValue = $setup[$theKey];
680  if ((int)$theKey && strpos($theKey, '.') === false) {
681  $conf = $setup[$theKey . '.'];
682  $content .= $this->cObjGetSingle($theValue, $conf, $addKey . $theKey);
683  }
684  }
685  return $content;
686  }
687 
697  public function cObjGetSingle($name, $conf, $TSkey = '__')
698  {
699  $content = '';
700  // Checking that the function is not called eternally. This is done by interrupting at a depth of 100
701  $this->getTypoScriptFrontendController()->cObjectDepthCounter--;
702  if ($this->getTypoScriptFrontendController()->cObjectDepthCounter > 0) {
703  $timeTracker = $this->getTimeTracker();
704  $name = trim($name);
705  if ($timeTracker->LR) {
706  $timeTracker->push($TSkey, $name);
707  }
708  // Checking if the COBJ is a reference to another object. (eg. name of 'blabla.blabla = < styles.something')
709  if ($name[0] === '<') {
710  $key = trim(substr($name, 1));
711  $cF = GeneralUtility::makeInstance(TypoScriptParser::class);
712  // $name and $conf is loaded with the referenced values.
713  $confOverride = is_array($conf) ? $conf : [];
714  list($name, $conf) = $cF->getVal($key, $this->getTypoScriptFrontendController()->tmpl->setup);
715  $conf = array_replace_recursive(is_array($conf) ? $conf : [], $confOverride);
716  // Getting the cObject
717  $timeTracker->incStackPointer();
718  $content .= $this->cObjGetSingle($name, $conf, $key);
719  $timeTracker->decStackPointer();
720  } else {
721  $hooked = false;
722  // Application defined cObjects
723  if (!empty($this->cObjHookObjectsRegistry[$name])) {
724  if (empty($this->cObjHookObjectsArr[$name])) {
725  $this->cObjHookObjectsArr[$name] = GeneralUtility::getUserObj($this->cObjHookObjectsRegistry[$name]);
726  }
727  $hookObj = $this->cObjHookObjectsArr[$name];
728  if (method_exists($hookObj, 'cObjGetSingleExt')) {
729  $content .= $hookObj->cObjGetSingleExt($name, $conf, $TSkey, $this);
730  $hooked = true;
731  }
732  }
733  if (!$hooked) {
734  $contentObject = $this->getContentObject($name);
735  if ($contentObject) {
736  $content .= $this->render($contentObject, $conf);
737  } else {
738  // Call hook functions for extra processing
739  if ($name && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['cObjTypeAndClassDefault'])) {
740  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['cObjTypeAndClassDefault'] as $classData) {
741  $hookObject = GeneralUtility::getUserObj($classData);
742  if (!$hookObject instanceof ContentObjectGetSingleHookInterface) {
743  throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetSingleHookInterface::class, 1195043731);
744  }
746  $content .= $hookObject->getSingleContentObject($name, (array)$conf, $TSkey, $this);
747  }
748  } else {
749  // Log error in AdminPanel
750  $warning = sprintf('Content Object "%s" does not exist', $name);
751  $timeTracker->setTSlogMessage($warning, 2);
752  }
753  }
754  }
755  }
756  if ($timeTracker->LR) {
757  $timeTracker->pull($content);
758  }
759  }
760  // Increasing on exit...
761  $this->getTypoScriptFrontendController()->cObjectDepthCounter++;
762  return $content;
763  }
764 
774  public function getContentObject($name)
775  {
776  if (!isset($this->contentObjectClassMap[$name])) {
777  return null;
778  }
779  $fullyQualifiedClassName = $this->contentObjectClassMap[$name];
780  $contentObject = GeneralUtility::makeInstance($fullyQualifiedClassName, $this);
781  if (!($contentObject instanceof AbstractContentObject)) {
782  throw new ContentRenderingException(sprintf('Registered content object class name "%s" must be an instance of AbstractContentObject, but is not!', $fullyQualifiedClassName), 1422564295);
783  }
784  return $contentObject;
785  }
786 
787  /********************************************
788  *
789  * Functions rendering content objects (cObjects)
790  *
791  ********************************************/
792 
804  public function render(AbstractContentObject $contentObject, $configuration = [])
805  {
806  $content = '';
807 
808  // Evaluate possible cache and return
809  $cacheConfiguration = isset($configuration['cache.']) ? $configuration['cache.'] : null;
810  if ($cacheConfiguration !== null) {
811  unset($configuration['cache.']);
812  $cache = $this->getFromCache($cacheConfiguration);
813  if ($cache !== false) {
814  return $cache;
815  }
816  }
817 
818  // Render content
819  try {
820  $content .= $contentObject->render($configuration);
821  } catch (ContentRenderingException $exception) {
822  // Content rendering Exceptions indicate a critical problem which should not be
823  // caught e.g. when something went wrong with Exception handling itself
824  throw $exception;
825  } catch (\Exception $exception) {
826  $exceptionHandler = $this->createExceptionHandler($configuration);
827  if ($exceptionHandler === null) {
828  throw $exception;
829  }
830  $content = $exceptionHandler->handle($exception, $contentObject, $configuration);
831  }
832 
833  // Store cache
834  if ($cacheConfiguration !== null) {
835  $key = $this->calculateCacheKey($cacheConfiguration);
836  if (!empty($key)) {
838  $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash');
839  $tags = $this->calculateCacheTags($cacheConfiguration);
840  $lifetime = $this->calculateCacheLifetime($cacheConfiguration);
841  $cacheFrontend->set($key, $content, $tags, $lifetime);
842  }
843  }
844 
845  return $content;
846  }
847 
856  protected function createExceptionHandler($configuration = [])
857  {
858  $exceptionHandler = null;
859  $exceptionHandlerClassName = $this->determineExceptionHandlerClassName($configuration);
860  if (!empty($exceptionHandlerClassName)) {
861  $exceptionHandler = GeneralUtility::makeInstance($exceptionHandlerClassName, $this->mergeExceptionHandlerConfiguration($configuration));
862  if (!$exceptionHandler instanceof ExceptionHandlerInterface) {
863  throw new ContentRenderingException('An exception handler was configured but the class does not exist or does not implement the ExceptionHandlerInterface', 1403653369);
864  }
865  }
866 
867  return $exceptionHandler;
868  }
869 
876  protected function determineExceptionHandlerClassName($configuration)
877  {
878  $exceptionHandlerClassName = null;
879  $tsfe = $this->getTypoScriptFrontendController();
880  if (!isset($tsfe->config['config']['contentObjectExceptionHandler'])) {
881  if (GeneralUtility::getApplicationContext()->isProduction()) {
882  $exceptionHandlerClassName = '1';
883  }
884  } else {
885  $exceptionHandlerClassName = $tsfe->config['config']['contentObjectExceptionHandler'];
886  }
887 
888  if (isset($configuration['exceptionHandler'])) {
889  $exceptionHandlerClassName = $configuration['exceptionHandler'];
890  }
891 
892  if ($exceptionHandlerClassName === '1') {
893  $exceptionHandlerClassName = ProductionExceptionHandler::class;
894  }
895 
896  return $exceptionHandlerClassName;
897  }
898 
906  protected function mergeExceptionHandlerConfiguration($configuration)
907  {
908  $exceptionHandlerConfiguration = [];
909  $tsfe = $this->getTypoScriptFrontendController();
910  if (!empty($tsfe->config['config']['contentObjectExceptionHandler.'])) {
911  $exceptionHandlerConfiguration = $tsfe->config['config']['contentObjectExceptionHandler.'];
912  }
913  if (!empty($configuration['exceptionHandler.'])) {
914  $exceptionHandlerConfiguration = array_replace_recursive($exceptionHandlerConfiguration, $configuration['exceptionHandler.']);
915  }
916 
917  return $exceptionHandlerConfiguration;
918  }
919 
928  public function getUserObjectType()
929  {
930  return $this->userObjectType;
931  }
932 
939  {
940  $this->userObjectType = $userObjectType;
941  }
942 
946  public function convertToUserIntObject()
947  {
948  if ($this->userObjectType !== self::OBJECTTYPE_USER) {
949  $this->getTimeTracker()->setTSlogMessage(self::class . '::convertToUserIntObject() is called in the wrong context or for the wrong object type', 2);
950  } else {
951  $this->doConvertToUserIntObject = true;
952  }
953  }
954 
955  /************************************
956  *
957  * Various helper functions for content objects:
958  *
959  ************************************/
967  public function readFlexformIntoConf($flexData, &$conf, $recursive = false)
968  {
969  if ($recursive === false && is_string($flexData)) {
970  $flexData = GeneralUtility::xml2array($flexData, 'T3');
971  }
972  if (is_array($flexData) && isset($flexData['data']['sDEF']['lDEF'])) {
973  $flexData = $flexData['data']['sDEF']['lDEF'];
974  }
975  if (!is_array($flexData)) {
976  return;
977  }
978  foreach ($flexData as $key => $value) {
979  if (!is_array($value)) {
980  continue;
981  }
982  if (isset($value['el'])) {
983  if (is_array($value['el']) && !empty($value['el'])) {
984  foreach ($value['el'] as $ekey => $element) {
985  if (isset($element['vDEF'])) {
986  $conf[$ekey] = $element['vDEF'];
987  } else {
988  if (is_array($element)) {
989  $this->readFlexformIntoConf($element, $conf[$key][key($element)][$ekey], true);
990  } else {
991  $this->readFlexformIntoConf($element, $conf[$key][$ekey], true);
992  }
993  }
994  }
995  } else {
996  $this->readFlexformIntoConf($value['el'], $conf[$key], true);
997  }
998  }
999  if (isset($value['vDEF'])) {
1000  $conf[$key] = $value['vDEF'];
1001  }
1002  }
1003  }
1004 
1013  public function getSlidePids($pidList, $pidConf)
1014  {
1015  $pidList = isset($pidConf) ? trim($this->stdWrap($pidList, $pidConf)) : trim($pidList);
1016  if ($pidList === '') {
1017  $pidList = 'this';
1018  }
1019  $tsfe = $this->getTypoScriptFrontendController();
1020  $listArr = null;
1021  if (trim($pidList)) {
1022  $listArr = GeneralUtility::intExplode(',', str_replace('this', $tsfe->contentPid, $pidList));
1023  $listArr = $this->checkPidArray($listArr);
1024  }
1025  $pidList = [];
1026  if (is_array($listArr) && !empty($listArr)) {
1027  foreach ($listArr as $uid) {
1028  $page = $tsfe->sys_page->getPage($uid);
1029  if (!$page['is_siteroot']) {
1030  $pidList[] = $page['pid'];
1031  }
1032  }
1033  }
1034  return implode(',', $pidList);
1035  }
1036 
1048  public function cImage($file, $conf)
1049  {
1050  $tsfe = $this->getTypoScriptFrontendController();
1051  $info = $this->getImgResource($file, $conf['file.']);
1052  $tsfe->lastImageInfo = $info;
1053  if (!is_array($info)) {
1054  return '';
1055  }
1056  if (is_file(PATH_site . $info['3'])) {
1057  $source = $tsfe->absRefPrefix . str_replace('%2F', '/', rawurlencode($info['3']));
1058  } else {
1059  $source = $info[3];
1060  }
1061 
1062  $layoutKey = $this->stdWrap($conf['layoutKey'], $conf['layoutKey.']);
1063  $imageTagTemplate = $this->getImageTagTemplate($layoutKey, $conf);
1064  $sourceCollection = $this->getImageSourceCollection($layoutKey, $conf, $file);
1065 
1066  // This array is used to collect the image-refs on the page...
1067  $tsfe->imagesOnPage[] = $source;
1068  $altParam = $this->getAltParam($conf);
1069  $params = $this->stdWrapValue('params', $conf);
1070  if ($params !== '' && $params[0] !== ' ') {
1071  $params = ' ' . $params;
1072  }
1073 
1074  $imageTagValues = [
1075  'width' => (int)$info[0],
1076  'height' => (int)$info[1],
1077  'src' => htmlspecialchars($source),
1078  'params' => $params,
1079  'altParams' => $altParam,
1080  'border' => $this->getBorderAttr(' border="' . (int)$conf['border'] . '"'),
1081  'sourceCollection' => $sourceCollection,
1082  'selfClosingTagSlash' => (!empty($tsfe->xhtmlDoctype) ? ' /' : ''),
1083  ];
1084 
1085  $theValue = $this->templateService->substituteMarkerArray($imageTagTemplate, $imageTagValues, '###|###', true, true);
1086 
1087  $linkWrap = isset($conf['linkWrap.']) ? $this->stdWrap($conf['linkWrap'], $conf['linkWrap.']) : $conf['linkWrap'];
1088  if ($linkWrap) {
1089  $theValue = $this->linkWrap($theValue, $linkWrap);
1090  } elseif ($conf['imageLinkWrap']) {
1091  $originalFile = !empty($info['originalFile']) ? $info['originalFile'] : $info['origFile'];
1092  $theValue = $this->imageLinkWrap($theValue, $originalFile, $conf['imageLinkWrap.']);
1093  }
1094  $wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
1095  if ((string)$wrap !== '') {
1096  $theValue = $this->wrap($theValue, $conf['wrap']);
1097  }
1098  return $theValue;
1099  }
1100 
1108  public function getBorderAttr($borderAttr)
1109  {
1110  $tsfe = $this->getTypoScriptFrontendController();
1111  $docType = $tsfe->xhtmlDoctype;
1112  if (
1113  $docType !== 'xhtml_strict' && $docType !== 'xhtml_11'
1114  && $tsfe->config['config']['doctype'] !== 'html5'
1115  && !$tsfe->config['config']['disableImgBorderAttr']
1116  ) {
1117  return $borderAttr;
1118  }
1119  return '';
1120  }
1121 
1130  public function getImageTagTemplate($layoutKey, $conf)
1131  {
1132  if ($layoutKey && isset($conf['layout.']) && isset($conf['layout.'][$layoutKey . '.'])) {
1133  $imageTagLayout = $this->stdWrap($conf['layout.'][$layoutKey . '.']['element'], $conf['layout.'][$layoutKey . '.']['element.']);
1134  } else {
1135  $imageTagLayout = '<img src="###SRC###" width="###WIDTH###" height="###HEIGHT###" ###PARAMS### ###ALTPARAMS### ###BORDER######SELFCLOSINGTAGSLASH###>';
1136  }
1137  return $imageTagLayout;
1138  }
1139 
1149  public function getImageSourceCollection($layoutKey, $conf, $file)
1150  {
1151  $sourceCollection = '';
1152  if ($layoutKey && $conf['sourceCollection.'] && ($conf['layout.'][$layoutKey . '.']['source'] || $conf['layout.'][$layoutKey . '.']['source.'])) {
1153 
1154  // find active sourceCollection
1155  $activeSourceCollections = [];
1156  foreach ($conf['sourceCollection.'] as $sourceCollectionKey => $sourceCollectionConfiguration) {
1157  if (substr($sourceCollectionKey, -1) === '.') {
1158  if (empty($sourceCollectionConfiguration['if.']) || $this->checkIf($sourceCollectionConfiguration['if.'])) {
1159  $activeSourceCollections[] = $sourceCollectionConfiguration;
1160  }
1161  }
1162  }
1163 
1164  // apply option split to configurations
1165  $tsfe = $this->getTypoScriptFrontendController();
1166  $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
1167  $srcLayoutOptionSplitted = $typoScriptService->explodeConfigurationForOptionSplit((array)$conf['layout.'][$layoutKey . '.'], count($activeSourceCollections));
1168 
1169  // render sources
1170  foreach ($activeSourceCollections as $key => $sourceConfiguration) {
1171  $sourceLayout = $this->stdWrap($srcLayoutOptionSplitted[$key]['source'], $srcLayoutOptionSplitted[$key]['source.']);
1172 
1173  $sourceRenderConfiguration = [
1174  'file' => $file,
1175  'file.' => $conf['file.']
1176  ];
1177 
1178  if (isset($sourceConfiguration['quality']) || isset($sourceConfiguration['quality.'])) {
1179  $imageQuality = isset($sourceConfiguration['quality']) ? $sourceConfiguration['quality'] : '';
1180  if (isset($sourceConfiguration['quality.'])) {
1181  $imageQuality = $this->stdWrap($sourceConfiguration['quality'], $sourceConfiguration['quality.']);
1182  }
1183  if ($imageQuality) {
1184  $sourceRenderConfiguration['file.']['params'] = '-quality ' . (int)$imageQuality;
1185  }
1186  }
1187 
1188  if (isset($sourceConfiguration['pixelDensity'])) {
1189  $pixelDensity = (int)$this->stdWrap($sourceConfiguration['pixelDensity'], $sourceConfiguration['pixelDensity.']);
1190  } else {
1191  $pixelDensity = 1;
1192  }
1193  $dimensionKeys = ['width', 'height', 'maxW', 'minW', 'maxH', 'minH', 'maxWidth', 'maxHeight', 'XY'];
1194  foreach ($dimensionKeys as $dimensionKey) {
1195  $dimension = $this->stdWrap($sourceConfiguration[$dimensionKey], $sourceConfiguration[$dimensionKey . '.']);
1196  if (!$dimension) {
1197  $dimension = $this->stdWrap($conf['file.'][$dimensionKey], $conf['file.'][$dimensionKey . '.']);
1198  }
1199  if ($dimension) {
1200  if (strstr($dimension, 'c') !== false && ($dimensionKey === 'width' || $dimensionKey === 'height')) {
1201  $dimensionParts = explode('c', $dimension, 2);
1202  $dimension = ((int)$dimensionParts[0] * $pixelDensity) . 'c';
1203  if ($dimensionParts[1]) {
1204  $dimension .= $dimensionParts[1];
1205  }
1206  } elseif ($dimensionKey === 'XY') {
1207  $dimensionParts = GeneralUtility::intExplode(',', $dimension, false, 2);
1208  $dimension = $dimensionParts[0] * $pixelDensity;
1209  if ($dimensionParts[1]) {
1210  $dimension .= ',' . $dimensionParts[1] * $pixelDensity;
1211  }
1212  } else {
1213  $dimension = (int)$dimension * $pixelDensity;
1214  }
1215  $sourceRenderConfiguration['file.'][$dimensionKey] = $dimension;
1216  // Remove the stdWrap properties for dimension as they have been processed already above.
1217  unset($sourceRenderConfiguration['file.'][$dimensionKey . '.']);
1218  }
1219  }
1220  $sourceInfo = $this->getImgResource($sourceRenderConfiguration['file'], $sourceRenderConfiguration['file.']);
1221  if ($sourceInfo) {
1222  $sourceConfiguration['width'] = $sourceInfo[0];
1223  $sourceConfiguration['height'] = $sourceInfo[1];
1224  $urlPrefix = '';
1225  if (parse_url($sourceInfo[3], PHP_URL_HOST) === null) {
1226  $urlPrefix = $tsfe->absRefPrefix;
1227  }
1228  $sourceConfiguration['src'] = htmlspecialchars($urlPrefix . $sourceInfo[3]);
1229  $sourceConfiguration['selfClosingTagSlash'] = !empty($tsfe->xhtmlDoctype) ? ' /' : '';
1230 
1231  $oneSourceCollection = $this->templateService->substituteMarkerArray($sourceLayout, $sourceConfiguration, '###|###', true, true);
1232 
1233  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImageSourceCollection'])) {
1234  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImageSourceCollection'] as $classData) {
1235  $hookObject = GeneralUtility::getUserObj($classData);
1236  if (!$hookObject instanceof ContentObjectOneSourceCollectionHookInterface) {
1237  throw new \UnexpectedValueException(
1238  '$hookObject must implement interface ' . ContentObjectOneSourceCollectionHookInterface::class,
1239  1380007853
1240  );
1241  }
1242  $oneSourceCollection = $hookObject->getOneSourceCollection((array)$sourceRenderConfiguration, (array)$sourceConfiguration, $oneSourceCollection, $this);
1243  }
1244  }
1245 
1246  $sourceCollection .= $oneSourceCollection;
1247  }
1248  }
1249  }
1250  return $sourceCollection;
1251  }
1252 
1262  public function imageLinkWrap($string, $imageFile, $conf)
1263  {
1264  $string = (string)$string;
1265  $enable = isset($conf['enable.']) ? $this->stdWrap($conf['enable'], $conf['enable.']) : $conf['enable'];
1266  if (!$enable) {
1267  return $string;
1268  }
1269  $content = (string)$this->typoLink($string, $conf['typolink.']);
1270  if (isset($conf['file.'])) {
1271  $imageFile = $this->stdWrap($imageFile, $conf['file.']);
1272  }
1273 
1274  if ($imageFile instanceof File) {
1275  $file = $imageFile;
1276  } elseif ($imageFile instanceof FileReference) {
1277  $file = $imageFile->getOriginalFile();
1278  } else {
1279  if (MathUtility::canBeInterpretedAsInteger($imageFile)) {
1280  $file = ResourceFactory::getInstance()->getFileObject((int)$imageFile);
1281  } else {
1282  $file = ResourceFactory::getInstance()->getFileObjectFromCombinedIdentifier($imageFile);
1283  }
1284  }
1285 
1286  // Create imageFileLink if not created with typolink
1287  if ($content === $string) {
1288  $parameterNames = ['width', 'height', 'effects', 'bodyTag', 'title', 'wrap', 'crop'];
1289  $parameters = [];
1290  $sample = isset($conf['sample.']) ? $this->stdWrap($conf['sample'], $conf['sample.']) : $conf['sample'];
1291  if ($sample) {
1292  $parameters['sample'] = 1;
1293  }
1294  foreach ($parameterNames as $parameterName) {
1295  if (isset($conf[$parameterName . '.'])) {
1296  $conf[$parameterName] = $this->stdWrap($conf[$parameterName], $conf[$parameterName . '.']);
1297  }
1298  if (isset($conf[$parameterName]) && $conf[$parameterName]) {
1299  $parameters[$parameterName] = $conf[$parameterName];
1300  }
1301  }
1302  $parametersEncoded = base64_encode(serialize($parameters));
1303  $hmac = GeneralUtility::hmac(implode('|', [$file->getUid(), $parametersEncoded]));
1304  $params = '&md5=' . $hmac;
1305  foreach (str_split($parametersEncoded, 64) as $index => $chunk) {
1306  $params .= '&parameters' . rawurlencode('[') . $index . rawurlencode(']') . '=' . rawurlencode($chunk);
1307  }
1308  $url = $this->getTypoScriptFrontendController()->absRefPrefix . 'index.php?eID=tx_cms_showpic&file=' . $file->getUid() . $params;
1309  $directImageLink = isset($conf['directImageLink.']) ? $this->stdWrap($conf['directImageLink'], $conf['directImageLink.']) : $conf['directImageLink'];
1310  if ($directImageLink) {
1311  $imgResourceConf = [
1312  'file' => $imageFile,
1313  'file.' => $conf
1314  ];
1315  $url = $this->cObjGetSingle('IMG_RESOURCE', $imgResourceConf);
1316  if (!$url) {
1317  // If no imagemagick / gm is available
1318  $url = $imageFile;
1319  }
1320  }
1321  // Create TARGET-attribute only if the right doctype is used
1322  $target = '';
1323  $xhtmlDocType = $this->getTypoScriptFrontendController()->xhtmlDoctype;
1324  if ($xhtmlDocType !== 'xhtml_strict' && $xhtmlDocType !== 'xhtml_11') {
1325  $target = isset($conf['target.'])
1326  ? (string)$this->stdWrap($conf['target'], $conf['target.'])
1327  : (string)$conf['target'];
1328  if ($target === '') {
1329  $target = 'thePicture';
1330  }
1331  }
1332  $a1 = '';
1333  $a2 = '';
1334  $conf['JSwindow'] = isset($conf['JSwindow.']) ? $this->stdWrap($conf['JSwindow'], $conf['JSwindow.']) : $conf['JSwindow'];
1335  if ($conf['JSwindow']) {
1336  if ($conf['JSwindow.']['altUrl'] || $conf['JSwindow.']['altUrl.']) {
1337  $altUrl = isset($conf['JSwindow.']['altUrl.']) ? $this->stdWrap($conf['JSwindow.']['altUrl'], $conf['JSwindow.']['altUrl.']) : $conf['JSwindow.']['altUrl'];
1338  if ($altUrl) {
1339  $url = $altUrl . ($conf['JSwindow.']['altUrl_noDefaultParams'] ? '' : '?file=' . rawurlencode($imageFile) . $params);
1340  }
1341  }
1342 
1343  $processedFile = $file->process('Image.CropScaleMask', $conf);
1344  $JSwindowExpand = isset($conf['JSwindow.']['expand.']) ? $this->stdWrap($conf['JSwindow.']['expand'], $conf['JSwindow.']['expand.']) : $conf['JSwindow.']['expand'];
1345  $offset = GeneralUtility::intExplode(',', $JSwindowExpand . ',');
1346  $newWindow = isset($conf['JSwindow.']['newWindow.']) ? $this->stdWrap($conf['JSwindow.']['newWindow'], $conf['JSwindow.']['newWindow.']) : $conf['JSwindow.']['newWindow'];
1347  $onClick = 'openPic('
1348  . GeneralUtility::quoteJSvalue($this->getTypoScriptFrontendController()->baseUrlWrap($url)) . ','
1349  . '\'' . ($newWindow ? md5($url) : 'thePicture') . '\','
1350  . GeneralUtility::quoteJSvalue('width=' . ($processedFile->getProperty('width') + $offset[0])
1351  . ',height=' . ($processedFile->getProperty('height') + $offset[1]) . ',status=0,menubar=0')
1352  . '); return false;';
1353  $a1 = '<a href="' . htmlspecialchars($url) . '"'
1354  . ' onclick="' . htmlspecialchars($onClick) . '"'
1355  . ($target !== '' ? ' target="' . htmlspecialchars($target) . '"' : '')
1356  . $this->getTypoScriptFrontendController()->ATagParams . '>';
1357  $a2 = '</a>';
1358  $this->getTypoScriptFrontendController()->setJS('openPic');
1359  } else {
1360  $conf['linkParams.']['parameter'] = $url;
1361  $string = $this->typoLink($string, $conf['linkParams.']);
1362  }
1363  if (isset($conf['stdWrap.'])) {
1364  $string = $this->stdWrap($string, $conf['stdWrap.']);
1365  }
1366  $content = $a1 . $string . $a2;
1367  }
1368  return $content;
1369  }
1370 
1380  public function fileResource($fName, $addParams = 'alt="" title=""')
1381  {
1382  GeneralUtility::logDeprecatedFunction();
1383  $tsfe = $this->getTypoScriptFrontendController();
1384  $incFile = $tsfe->tmpl->getFileName($fName);
1385  if ($incFile && file_exists($incFile)) {
1386  $fileInfo = GeneralUtility::split_fileref($incFile);
1387  $extension = $fileInfo['fileext'];
1388  if ($extension === 'jpg' || $extension === 'jpeg' || $extension === 'gif' || $extension === 'png') {
1389  $imgFile = $incFile;
1390  $imgInfo = @getimagesize($imgFile);
1391  return '<img src="' . htmlspecialchars($tsfe->absRefPrefix . $imgFile) . '" width="' . (int)$imgInfo[0] . '" height="' . (int)$imgInfo[1] . '"' . $this->getBorderAttr(' border="0"') . ' ' . $addParams . ' />';
1392  }
1393  if (filesize($incFile) < 1024 * 1024) {
1394  return file_get_contents($incFile);
1395  }
1396  }
1397  return '';
1398  }
1399 
1408  public function lastChanged($tstamp)
1409  {
1410  $tstamp = (int)$tstamp;
1411  $tsfe = $this->getTypoScriptFrontendController();
1412  if ($tstamp > (int)$tsfe->register['SYS_LASTCHANGED']) {
1413  $tsfe->register['SYS_LASTCHANGED'] = $tstamp;
1414  }
1415  }
1416 
1426  public function linkWrap($content, $wrap)
1427  {
1428  $wrapArr = explode('|', $wrap);
1429  if (preg_match('/\\{([0-9]*)\\}/', $wrapArr[0], $reg)) {
1430  if ($uid = $this->getTypoScriptFrontendController()->tmpl->rootLine[$reg[1]]['uid']) {
1431  $wrapArr[0] = str_replace($reg[0], $uid, $wrapArr[0]);
1432  }
1433  }
1434  return trim($wrapArr[0]) . $content . trim($wrapArr[1]);
1435  }
1436 
1446  public function getAltParam($conf, $longDesc = true)
1447  {
1448  $altText = isset($conf['altText.']) ? trim($this->stdWrap($conf['altText'], $conf['altText.'])) : trim($conf['altText']);
1449  $titleText = isset($conf['titleText.']) ? trim($this->stdWrap($conf['titleText'], $conf['titleText.'])) : trim($conf['titleText']);
1450  if (isset($conf['longdescURL.']) && $this->getTypoScriptFrontendController()->config['config']['doctype'] != 'html5') {
1451  $longDescUrl = $this->typoLink_URL($conf['longdescURL.']);
1452  } else {
1453  $longDescUrl = trim($conf['longdescURL']);
1454  }
1455  $longDescUrl = strip_tags($longDescUrl);
1456 
1457  // "alt":
1458  $altParam = ' alt="' . htmlspecialchars($altText) . '"';
1459  // "title":
1460  $emptyTitleHandling = isset($conf['emptyTitleHandling.']) ? $this->stdWrap($conf['emptyTitleHandling'], $conf['emptyTitleHandling.']) : $conf['emptyTitleHandling'];
1461  // Choices: 'keepEmpty' | 'useAlt' | 'removeAttr'
1462  if ($titleText || $emptyTitleHandling === 'keepEmpty') {
1463  $altParam .= ' title="' . htmlspecialchars($titleText) . '"';
1464  } elseif (!$titleText && $emptyTitleHandling === 'useAlt') {
1465  $altParam .= ' title="' . htmlspecialchars($altText) . '"';
1466  }
1467  // "longDesc" URL
1468  if ($longDesc && !empty($longDescUrl)) {
1469  $altParam .= ' longdesc="' . htmlspecialchars($longDescUrl) . '"';
1470  }
1471  return $altParam;
1472  }
1473 
1483  public function getATagParams($conf, $addGlobal = 1)
1484  {
1485  $aTagParams = '';
1486  if ($conf['ATagParams.']) {
1487  $aTagParams = ' ' . $this->stdWrap($conf['ATagParams'], $conf['ATagParams.']);
1488  } elseif ($conf['ATagParams']) {
1489  $aTagParams = ' ' . $conf['ATagParams'];
1490  }
1491  if ($addGlobal) {
1492  $aTagParams = ' ' . trim($this->getTypoScriptFrontendController()->ATagParams . $aTagParams);
1493  }
1494  // Extend params
1495  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'])) {
1496  $_params = [
1497  'conf' => &$conf,
1498  'aTagParams' => &$aTagParams
1499  ];
1500  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getATagParamsPostProc'] as $objRef) {
1501  $processor =& GeneralUtility::getUserObj($objRef);
1502  $aTagParams = $processor->process($_params, $this);
1503  }
1504  }
1505 
1506  $aTagParams = trim($aTagParams);
1507  if (!empty($aTagParams)) {
1508  $aTagParams = ' ' . $aTagParams;
1509  }
1510 
1511  return $aTagParams;
1512  }
1513 
1522  public function extLinkATagParams($URL, $TYPE)
1523  {
1524  $out = '';
1525  if ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['extLinkATagParamsHandler']) {
1526  $extLinkATagParamsHandler = GeneralUtility::getUserObj($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['extLinkATagParamsHandler']);
1527  if (method_exists($extLinkATagParamsHandler, 'main')) {
1528  $out .= trim($extLinkATagParamsHandler->main($URL, $TYPE, $this));
1529  }
1530  }
1531  return trim($out) ? ' ' . trim($out) : '';
1532  }
1533 
1534  /***********************************************
1535  *
1536  * HTML template processing functions
1537  *
1538  ***********************************************/
1558  public function getSubpart($content, $marker)
1559  {
1560  GeneralUtility::logDeprecatedFunction();
1561  return $this->templateService->getSubpart($content, $marker);
1562  }
1563 
1577  public function substituteSubpart($content, $marker, $subpartContent, $recursive = 1)
1578  {
1579  GeneralUtility::logDeprecatedFunction();
1580  return $this->templateService->substituteSubpart($content, $marker, $subpartContent, $recursive);
1581  }
1582 
1591  public function substituteSubpartArray($content, array $subpartsContent)
1592  {
1593  GeneralUtility::logDeprecatedFunction();
1594  return $this->templateService->substituteSubpartArray($content, $subpartsContent);
1595  }
1596 
1608  public function substituteMarker($content, $marker, $markContent)
1609  {
1610  GeneralUtility::logDeprecatedFunction();
1611  return $this->templateService->substituteMarker($content, $marker, $markContent);
1612  }
1613 
1647  public function substituteMarkerArrayCached($content, array $markContentArray = null, array $subpartContentArray = null, array $wrappedSubpartContentArray = null)
1648  {
1649  GeneralUtility::logDeprecatedFunction();
1650  return $this->templateService->substituteMarkerArrayCached($content, $markContentArray, $subpartContentArray, $wrappedSubpartContentArray);
1651  }
1652 
1673  public function substituteMarkerArray($content, array $markContentArray, $wrap = '', $uppercase = false, $deleteUnused = false)
1674  {
1675  GeneralUtility::logDeprecatedFunction();
1676  return $this->templateService->substituteMarkerArray($content, $markContentArray, $wrap, $uppercase, $deleteUnused);
1677  }
1678 
1688  public function substituteMarkerInObject(&$tree, array $markContentArray)
1689  {
1690  GeneralUtility::logDeprecatedFunction();
1691  if (is_array($tree)) {
1692  foreach ($tree as $key => $value) {
1693  $this->templateService->substituteMarkerInObject($tree[$key], $markContentArray);
1694  }
1695  } else {
1696  $tree = $this->templateService->substituteMarkerArray($tree, $markContentArray);
1697  }
1698  return $tree;
1699  }
1700 
1712  public function substituteMarkerAndSubpartArrayRecursive($content, array $markersAndSubparts, $wrap = '', $uppercase = false, $deleteUnused = false)
1713  {
1714  GeneralUtility::logDeprecatedFunction();
1715  return $this->templateService->substituteMarkerAndSubpartArrayRecursive($content, $markersAndSubparts, $wrap, $uppercase, $deleteUnused);
1716  }
1717 
1731  public function fillInMarkerArray(array $markContentArray, array $row, $fieldList = '', $nl2br = true, $prefix = 'FIELD_', $HSC = false)
1732  {
1733  GeneralUtility::logDeprecatedFunction();
1734  $tsfe = $this->getTypoScriptFrontendController();
1735  return $this->templateService->fillInMarkerArray($markContentArray, $row, $fieldList, $nl2br, $prefix, $HSC, !empty($tsfe->xhtmlDoctype));
1736  }
1737 
1743  public function setCurrentFile($fileObject)
1744  {
1745  $this->currentFile = $fileObject;
1746  }
1747 
1753  public function getCurrentFile()
1754  {
1755  return $this->currentFile;
1756  }
1757 
1758  /***********************************************
1759  *
1760  * "stdWrap" + sub functions
1761  *
1762  ***********************************************/
1774  public function stdWrap($content = '', $conf = [])
1775  {
1776  $content = (string)$content;
1777  // If there is any hook object, activate all of the process and override functions.
1778  // The hook interface ContentObjectStdWrapHookInterface takes care that all 4 methods exist.
1779  if ($this->stdWrapHookObjects) {
1780  $conf['stdWrapPreProcess'] = 1;
1781  $conf['stdWrapOverride'] = 1;
1782  $conf['stdWrapProcess'] = 1;
1783  $conf['stdWrapPostProcess'] = 1;
1784  }
1785 
1786  if (!is_array($conf) || !$conf) {
1787  return $content;
1788  }
1789 
1790  // Cache handling
1791  if (is_array($conf['cache.'])) {
1792  $conf['cache.']['key'] = $this->stdWrap($conf['cache.']['key'], $conf['cache.']['key.']);
1793  $conf['cache.']['tags'] = $this->stdWrap($conf['cache.']['tags'], $conf['cache.']['tags.']);
1794  $conf['cache.']['lifetime'] = $this->stdWrap($conf['cache.']['lifetime'], $conf['cache.']['lifetime.']);
1795  $conf['cacheRead'] = 1;
1796  $conf['cacheStore'] = 1;
1797  }
1798  // The configuration is sorted and filtered by intersection with the defined stdWrapOrder.
1799  $sortedConf = array_keys(array_intersect_key($this->stdWrapOrder, $conf));
1800  // Functions types that should not make use of nested stdWrap function calls to avoid conflicts with internal TypoScript used by these functions
1801  $stdWrapDisabledFunctionTypes = 'cObject,functionName,stdWrap';
1802  // Additional Array to check whether a function has already been executed
1803  $isExecuted = [];
1804  // Additional switch to make sure 'required', 'if' and 'fieldRequired'
1805  // will still stop rendering immediately in case they return FALSE
1806  $this->stdWrapRecursionLevel++;
1807  $this->stopRendering[$this->stdWrapRecursionLevel] = false;
1808  // execute each function in the predefined order
1809  foreach ($sortedConf as $stdWrapName) {
1810  // eliminate the second key of a pair 'key'|'key.' to make sure functions get called only once and check if rendering has been stopped
1811  if (!$isExecuted[$stdWrapName] && !$this->stopRendering[$this->stdWrapRecursionLevel]) {
1812  $functionName = rtrim($stdWrapName, '.');
1813  $functionProperties = $functionName . '.';
1814  $functionType = $this->stdWrapOrder[$functionName];
1815  // If there is any code on the next level, check if it contains "official" stdWrap functions
1816  // if yes, execute them first - will make each function stdWrap aware
1817  // so additional stdWrap calls within the functions can be removed, since the result will be the same
1818  if (!empty($conf[$functionProperties]) && !GeneralUtility::inList($stdWrapDisabledFunctionTypes, $functionType)) {
1819  if (array_intersect_key($this->stdWrapOrder, $conf[$functionProperties])) {
1820  $conf[$functionName] = $this->stdWrap($conf[$functionName], $conf[$functionProperties]);
1821  }
1822  }
1823  // Check if key is still containing something, since it might have been changed by next level stdWrap before
1824  if ((isset($conf[$functionName]) || $conf[$functionProperties]) && ($functionType !== 'boolean' || $conf[$functionName])) {
1825  // Get just that part of $conf that is needed for the particular function
1826  $singleConf = [
1827  $functionName => $conf[$functionName],
1828  $functionProperties => $conf[$functionProperties]
1829  ];
1830  // In this special case 'spaceBefore' and 'spaceAfter' need additional stuff from 'space.''
1831  if ($functionName === 'spaceBefore' || $functionName === 'spaceAfter') {
1832  $singleConf['space.'] = $conf['space.'];
1833  }
1834  // Hand over the whole $conf array to the stdWrapHookObjects
1835  if ($functionType === 'hook') {
1836  $singleConf = $conf;
1837  }
1838  // Add both keys - with and without the dot - to the set of executed functions
1839  $isExecuted[$functionName] = true;
1840  $isExecuted[$functionProperties] = true;
1841  // Call the function with the prefix stdWrap_ to make sure nobody can execute functions just by adding their name to the TS Array
1842  $functionName = 'stdWrap_' . $functionName;
1843  $content = $this->{$functionName}($content, $singleConf);
1844  } elseif ($functionType === 'boolean' && !$conf[$functionName]) {
1845  $isExecuted[$functionName] = true;
1846  $isExecuted[$functionProperties] = true;
1847  }
1848  }
1849  }
1850  unset($this->stopRendering[$this->stdWrapRecursionLevel]);
1851  $this->stdWrapRecursionLevel--;
1852 
1853  return $content;
1854  }
1855 
1864  public function stdWrapValue($key, array $config, $defaultValue = '')
1865  {
1866  if (isset($config[$key])) {
1867  if (!isset($config[$key . '.'])) {
1868  return $config[$key];
1869  }
1870  } elseif (isset($config[$key . '.'])) {
1871  $config[$key] = '';
1872  } else {
1873  return $defaultValue;
1874  }
1875  $stdWrapped = $this->stdWrap($config[$key], $config[$key . '.']);
1876  return $stdWrapped ?: $defaultValue;
1877  }
1878 
1888  public function stdWrap_stdWrapPreProcess($content = '', $conf = [])
1889  {
1890  foreach ($this->stdWrapHookObjects as $hookObject) {
1892  $content = $hookObject->stdWrapPreProcess($content, $conf, $this);
1893  }
1894  return $content;
1895  }
1896 
1904  public function stdWrap_cacheRead($content = '', $conf = [])
1905  {
1906  if (!isset($conf['cache.'])) {
1907  return $content;
1908  }
1909  $result = $this->getFromCache($conf['cache.']);
1910  return $result === false ? $content : $result;
1911  }
1912 
1920  public function stdWrap_addPageCacheTags($content = '', $conf = [])
1921  {
1922  $tags = isset($conf['addPageCacheTags.'])
1923  ? $this->stdWrap($conf['addPageCacheTags'], $conf['addPageCacheTags.'])
1924  : $conf['addPageCacheTags'];
1925  if (!empty($tags)) {
1926  $cacheTags = GeneralUtility::trimExplode(',', $tags, true);
1927  $this->getTypoScriptFrontendController()->addCacheTags($cacheTags);
1928  }
1929  return $content;
1930  }
1931 
1939  public function stdWrap_setContentToCurrent($content = '')
1940  {
1941  $this->data[$this->currentValKey] = $content;
1942  return $content;
1943  }
1944 
1953  public function stdWrap_setCurrent($content = '', $conf = [])
1954  {
1955  $this->data[$this->currentValKey] = $conf['setCurrent'];
1956  return $content;
1957  }
1958 
1967  public function stdWrap_lang($content = '', $conf = [])
1968  {
1969  $tsfe = $this->getTypoScriptFrontendController();
1970  if (isset($conf['lang.']) && $tsfe->config['config']['language'] && isset($conf['lang.'][$tsfe->config['config']['language']])) {
1971  $content = $conf['lang.'][$tsfe->config['config']['language']];
1972  }
1973  return $content;
1974  }
1975 
1984  public function stdWrap_data($content = '', $conf = [])
1985  {
1986  $content = $this->getData($conf['data'], is_array($this->alternativeData) ? $this->alternativeData : $this->data);
1987  // This must be unset directly after
1988  $this->alternativeData = '';
1989  return $content;
1990  }
1991 
2000  public function stdWrap_field($content = '', $conf = [])
2001  {
2002  return $this->getFieldVal($conf['field']);
2003  }
2004 
2014  public function stdWrap_current($content = '', $conf = [])
2015  {
2016  return $this->data[$this->currentValKey];
2017  }
2018 
2028  public function stdWrap_cObject($content = '', $conf = [])
2029  {
2030  return $this->cObjGetSingle($conf['cObject'], $conf['cObject.'], '/stdWrap/.cObject');
2031  }
2032 
2042  public function stdWrap_numRows($content = '', $conf = [])
2043  {
2044  return $this->numRows($conf['numRows.']);
2045  }
2046 
2055  public function stdWrap_filelist($content = '', $conf = [])
2056  {
2057  return $this->filelist($conf['filelist']);
2058  }
2059 
2068  public function stdWrap_preUserFunc($content = '', $conf = [])
2069  {
2070  return $this->callUserFunction($conf['preUserFunc'], $conf['preUserFunc.'], $content);
2071  }
2072 
2082  public function stdWrap_stdWrapOverride($content = '', $conf = [])
2083  {
2084  foreach ($this->stdWrapHookObjects as $hookObject) {
2086  $content = $hookObject->stdWrapOverride($content, $conf, $this);
2087  }
2088  return $content;
2089  }
2090 
2099  public function stdWrap_override($content = '', $conf = [])
2100  {
2101  if (trim($conf['override'])) {
2102  $content = $conf['override'];
2103  }
2104  return $content;
2105  }
2106 
2116  public function stdWrap_preIfEmptyListNum($content = '', $conf = [])
2117  {
2118  return $this->listNum($content, $conf['preIfEmptyListNum'], $conf['preIfEmptyListNum.']['splitChar']);
2119  }
2120 
2129  public function stdWrap_ifNull($content = '', $conf = [])
2130  {
2131  return $content !== null ? $content : $conf['ifNull'];
2132  }
2133 
2143  public function stdWrap_ifEmpty($content = '', $conf = [])
2144  {
2145  if (!trim($content)) {
2146  $content = $conf['ifEmpty'];
2147  }
2148  return $content;
2149  }
2150 
2160  public function stdWrap_ifBlank($content = '', $conf = [])
2161  {
2162  if (trim($content) === '') {
2163  $content = $conf['ifBlank'];
2164  }
2165  return $content;
2166  }
2167 
2178  public function stdWrap_listNum($content = '', $conf = [])
2179  {
2180  return $this->listNum($content, $conf['listNum'], $conf['listNum.']['splitChar']);
2181  }
2182 
2190  public function stdWrap_trim($content = '')
2191  {
2192  return trim($content);
2193  }
2194 
2203  public function stdWrap_strPad($content = '', $conf = [])
2204  {
2205  // Must specify a length in conf for this to make sense
2206  $length = 0;
2207  // Padding with space is PHP-default
2208  $padWith = ' ';
2209  // Padding on the right side is PHP-default
2210  $padType = STR_PAD_RIGHT;
2211  if (!empty($conf['strPad.']['length'])) {
2212  $length = isset($conf['strPad.']['length.']) ? $this->stdWrap($conf['strPad.']['length'], $conf['strPad.']['length.']) : $conf['strPad.']['length'];
2213  $length = (int)$length;
2214  }
2215  if (isset($conf['strPad.']['padWith']) && (string)$conf['strPad.']['padWith'] !== '') {
2216  $padWith = isset($conf['strPad.']['padWith.']) ? $this->stdWrap($conf['strPad.']['padWith'], $conf['strPad.']['padWith.']) : $conf['strPad.']['padWith'];
2217  }
2218  if (!empty($conf['strPad.']['type'])) {
2219  $type = isset($conf['strPad.']['type.']) ? $this->stdWrap($conf['strPad.']['type'], $conf['strPad.']['type.']) : $conf['strPad.']['type'];
2220  if (strtolower($type) === 'left') {
2221  $padType = STR_PAD_LEFT;
2222  } elseif (strtolower($type) === 'both') {
2223  $padType = STR_PAD_BOTH;
2224  }
2225  }
2226  return str_pad($content, $length, $padWith, $padType);
2227  }
2228 
2240  public function stdWrap_stdWrap($content = '', $conf = [])
2241  {
2242  return $this->stdWrap($content, $conf['stdWrap.']);
2243  }
2244 
2254  public function stdWrap_stdWrapProcess($content = '', $conf = [])
2255  {
2256  foreach ($this->stdWrapHookObjects as $hookObject) {
2258  $content = $hookObject->stdWrapProcess($content, $conf, $this);
2259  }
2260  return $content;
2261  }
2262 
2271  public function stdWrap_required($content = '')
2272  {
2273  if ((string)$content === '') {
2274  $content = '';
2275  $this->stopRendering[$this->stdWrapRecursionLevel] = true;
2276  }
2277  return $content;
2278  }
2279 
2289  public function stdWrap_if($content = '', $conf = [])
2290  {
2291  if (empty($conf['if.']) || $this->checkIf($conf['if.'])) {
2292  return $content;
2293  }
2294  $this->stopRendering[$this->stdWrapRecursionLevel] = true;
2295  return '';
2296  }
2297 
2307  public function stdWrap_fieldRequired($content = '', $conf = [])
2308  {
2309  if (!trim($this->data[$conf['fieldRequired']])) {
2310  $content = '';
2311  $this->stopRendering[$this->stdWrapRecursionLevel] = true;
2312  }
2313  return $content;
2314  }
2315 
2326  public function stdWrap_csConv($content = '', $conf = [])
2327  {
2328  if (!empty($conf['csConv'])) {
2329  $output = mb_convert_encoding($content, 'utf-8', trim(strtolower($conf['csConv'])));
2330  return $output !== false && $output !== '' ? $output : $content;
2331  }
2332  return $content;
2333  }
2334 
2344  public function stdWrap_parseFunc($content = '', $conf = [])
2345  {
2346  return $this->parseFunc($content, $conf['parseFunc.'], $conf['parseFunc']);
2347  }
2348 
2358  public function stdWrap_HTMLparser($content = '', $conf = [])
2359  {
2360  if (is_array($conf['HTMLparser.'])) {
2361  $content = $this->HTMLparser_TSbridge($content, $conf['HTMLparser.']);
2362  }
2363  return $content;
2364  }
2365 
2375  public function stdWrap_split($content = '', $conf = [])
2376  {
2377  return $this->splitObj($content, $conf['split.']);
2378  }
2379 
2388  public function stdWrap_replacement($content = '', $conf = [])
2389  {
2390  return $this->replacement($content, $conf['replacement.']);
2391  }
2392 
2402  public function stdWrap_prioriCalc($content = '', $conf = [])
2403  {
2404  $content = MathUtility::calculateWithParentheses($content);
2405  if ($conf['prioriCalc'] === 'intval') {
2406  $content = (int)$content;
2407  }
2408  return $content;
2409  }
2410 
2422  public function stdWrap_char($content = '', $conf = [])
2423  {
2424  return chr((int)$conf['char']);
2425  }
2426 
2434  public function stdWrap_intval($content = '')
2435  {
2436  return (int)$content;
2437  }
2438 
2447  public function stdWrap_hash($content = '', array $conf = [])
2448  {
2449  $algorithm = isset($conf['hash.']) ? $this->stdWrap($conf['hash'], $conf['hash.']) : $conf['hash'];
2450  if (function_exists('hash') && in_array($algorithm, hash_algos())) {
2451  return hash($algorithm, $content);
2452  }
2453  // Non-existing hashing algorithm
2454  return '';
2455  }
2456 
2465  public function stdWrap_round($content = '', $conf = [])
2466  {
2467  return $this->round($content, $conf['round.']);
2468  }
2469 
2478  public function stdWrap_numberFormat($content = '', $conf = [])
2479  {
2480  return $this->numberFormat($content, $conf['numberFormat.']);
2481  }
2482 
2490  public function stdWrap_expandList($content = '')
2491  {
2492  return GeneralUtility::expandList($content);
2493  }
2494 
2504  public function stdWrap_date($content = '', $conf = [])
2505  {
2506  // Check for zero length string to mimic default case of date/gmdate.
2507  $content = (string)$content === '' ? $GLOBALS['EXEC_TIME'] : (int)$content;
2508  $content = $conf['date.']['GMT'] ? gmdate($conf['date'], $content) : date($conf['date'], $content);
2509  return $content;
2510  }
2511 
2521  public function stdWrap_strftime($content = '', $conf = [])
2522  {
2523  // Check for zero length string to mimic default case of strtime/gmstrftime
2524  $content = (string)$content === '' ? $GLOBALS['EXEC_TIME'] : (int)$content;
2525  $content = $conf['strftime.']['GMT'] ? gmstrftime($conf['strftime'], $content) : strftime($conf['strftime'], $content);
2526  if (!empty($conf['strftime.']['charset'])) {
2527  $output = mb_convert_encoding($content, 'utf-8', trim(strtolower($conf['strftime.']['charset'])));
2528  return $output ?: $content;
2529  }
2530  return $content;
2531  }
2532 
2541  public function stdWrap_strtotime($content = '', $conf = [])
2542  {
2543  if ($conf['strtotime'] !== '1') {
2544  $content .= ' ' . $conf['strtotime'];
2545  }
2546  return strtotime($content, $GLOBALS['EXEC_TIME']);
2547  }
2548 
2557  public function stdWrap_age($content = '', $conf = [])
2558  {
2559  return $this->calcAge((int)$GLOBALS['EXEC_TIME'] - (int)$content, $conf['age']);
2560  }
2561 
2571  public function stdWrap_case($content = '', $conf = [])
2572  {
2573  return $this->HTMLcaseshift($content, $conf['case']);
2574  }
2575 
2584  public function stdWrap_bytes($content = '', $conf = [])
2585  {
2586  return GeneralUtility::formatSize($content, $conf['bytes.']['labels'], $conf['bytes.']['base']);
2587  }
2588 
2597  public function stdWrap_substring($content = '', $conf = [])
2598  {
2599  return $this->substring($content, $conf['substring']);
2600  }
2601 
2610  public function stdWrap_removeBadHTML($content = '')
2611  {
2612  return $this->removeBadHTML($content);
2613  }
2614 
2623  public function stdWrap_cropHTML($content = '', $conf = [])
2624  {
2625  return $this->cropHTML($content, $conf['cropHTML']);
2626  }
2627 
2635  public function stdWrap_stripHtml($content = '')
2636  {
2637  return strip_tags($content);
2638  }
2639 
2648  public function stdWrap_crop($content = '', $conf = [])
2649  {
2650  return $this->crop($content, $conf['crop']);
2651  }
2652 
2660  public function stdWrap_rawUrlEncode($content = '')
2661  {
2662  return rawurlencode($content);
2663  }
2664 
2674  public function stdWrap_htmlSpecialChars($content = '', $conf = [])
2675  {
2676  if (!empty($conf['htmlSpecialChars.']['preserveEntities'])) {
2677  $content = htmlspecialchars($content, ENT_COMPAT, 'UTF-8', false);
2678  } else {
2679  $content = htmlspecialchars($content);
2680  }
2681  return $content;
2682  }
2683 
2692  public function stdWrap_encodeForJavaScriptValue($content = '')
2693  {
2694  return GeneralUtility::quoteJSvalue($content);
2695  }
2696 
2705  public function stdWrap_doubleBrTag($content = '', $conf = [])
2706  {
2707  return preg_replace('/\R{1,2}[\t\x20]*\R{1,2}/', $conf['doubleBrTag'], $content);
2708  }
2709 
2718  public function stdWrap_br($content = '')
2719  {
2720  return nl2br($content, !empty($this->getTypoScriptFrontendController()->xhtmlDoctype));
2721  }
2722 
2731  public function stdWrap_brTag($content = '', $conf = [])
2732  {
2733  return str_replace(LF, $conf['brTag'], $content);
2734  }
2735 
2745  public function stdWrap_encapsLines($content = '', $conf = [])
2746  {
2747  return $this->encaps_lineSplit($content, $conf['encapsLines.']);
2748  }
2749 
2757  public function stdWrap_keywords($content = '')
2758  {
2759  return $this->keywords($content);
2760  }
2761 
2771  public function stdWrap_innerWrap($content = '', $conf = [])
2772  {
2773  return $this->wrap($content, $conf['innerWrap']);
2774  }
2775 
2785  public function stdWrap_innerWrap2($content = '', $conf = [])
2786  {
2787  return $this->wrap($content, $conf['innerWrap2']);
2788  }
2789 
2800  public function stdWrap_fontTag($content = '', $conf = [])
2801  {
2803  return $this->wrap($content, $conf['fontTag']);
2804  }
2805 
2814  public function stdWrap_addParams($content = '', $conf = [])
2815  {
2816  return $this->addParams($content, $conf['addParams.']);
2817  }
2818 
2828  public function stdWrap_filelink($content = '', $conf = [])
2829  {
2830  return $this->filelink($content, $conf['filelink.']);
2831  }
2832 
2841  public function stdWrap_preCObject($content = '', $conf = [])
2842  {
2843  return $this->cObjGetSingle($conf['preCObject'], $conf['preCObject.'], '/stdWrap/.preCObject') . $content;
2844  }
2845 
2854  public function stdWrap_postCObject($content = '', $conf = [])
2855  {
2856  return $content . $this->cObjGetSingle($conf['postCObject'], $conf['postCObject.'], '/stdWrap/.postCObject');
2857  }
2858 
2868  public function stdWrap_wrapAlign($content = '', $conf = [])
2869  {
2870  $wrapAlign = trim($conf['wrapAlign']);
2871  if ($wrapAlign) {
2872  $content = $this->wrap($content, '<div style="text-align:' . htmlspecialchars($wrapAlign) . ';">|</div>');
2873  }
2874  return $content;
2875  }
2876 
2887  public function stdWrap_typolink($content = '', $conf = [])
2888  {
2889  return $this->typoLink($content, $conf['typolink.']);
2890  }
2891 
2900  public function stdWrap_TCAselectItem($content = '', $conf = [])
2901  {
2902  if (is_array($conf['TCAselectItem.'])) {
2903  $content = $this->TCAlookup($content, $conf['TCAselectItem.']);
2904  }
2905  return $content;
2906  }
2907 
2917  public function stdWrap_spaceBefore($content = '', $conf = [])
2918  {
2919  return $this->wrapSpace($content, trim($conf['spaceBefore']) . '|', $conf['space.']);
2920  }
2921 
2931  public function stdWrap_spaceAfter($content = '', $conf = [])
2932  {
2933  return $this->wrapSpace($content, '|' . trim($conf['spaceAfter']), $conf['space.']);
2934  }
2935 
2946  public function stdWrap_space($content = '', $conf = [])
2947  {
2948  return $this->wrapSpace($content, trim($conf['space']), $conf['space.']);
2949  }
2950 
2963  public function stdWrap_wrap($content = '', $conf = [])
2964  {
2965  return $this->wrap($content, $conf['wrap'], $conf['wrap.']['splitChar'] ? $conf['wrap.']['splitChar'] : '|');
2966  }
2967 
2977  public function stdWrap_noTrimWrap($content = '', $conf = [])
2978  {
2979  $splitChar = isset($conf['noTrimWrap.']['splitChar.'])
2980  ? $this->stdWrap($conf['noTrimWrap.']['splitChar'], $conf['noTrimWrap.']['splitChar.'])
2981  : $conf['noTrimWrap.']['splitChar'];
2982  if ($splitChar === null || $splitChar === '') {
2983  $splitChar = '|';
2984  }
2985  $content = $this->noTrimWrap(
2986  $content,
2987  $conf['noTrimWrap'],
2988  $splitChar
2989  );
2990  return $content;
2991  }
2992 
3002  public function stdWrap_wrap2($content = '', $conf = [])
3003  {
3004  return $this->wrap($content, $conf['wrap2'], $conf['wrap2.']['splitChar'] ? $conf['wrap2.']['splitChar'] : '|');
3005  }
3006 
3016  public function stdWrap_dataWrap($content = '', $conf = [])
3017  {
3018  return $this->dataWrap($content, $conf['dataWrap']);
3019  }
3020 
3029  public function stdWrap_prepend($content = '', $conf = [])
3030  {
3031  return $this->cObjGetSingle($conf['prepend'], $conf['prepend.'], '/stdWrap/.prepend') . $content;
3032  }
3033 
3042  public function stdWrap_append($content = '', $conf = [])
3043  {
3044  return $content . $this->cObjGetSingle($conf['append'], $conf['append.'], '/stdWrap/.append');
3045  }
3046 
3056  public function stdWrap_wrap3($content = '', $conf = [])
3057  {
3058  return $this->wrap($content, $conf['wrap3'], $conf['wrap3.']['splitChar'] ? $conf['wrap3.']['splitChar'] : '|');
3059  }
3060 
3069  public function stdWrap_orderedStdWrap($content = '', $conf = [])
3070  {
3071  $sortedKeysArray = ArrayUtility::filterAndSortByNumericKeys($conf['orderedStdWrap.'], true);
3072  foreach ($sortedKeysArray as $key) {
3073  $content = $this->stdWrap($content, $conf['orderedStdWrap.'][$key . '.']);
3074  }
3075  return $content;
3076  }
3077 
3086  public function stdWrap_outerWrap($content = '', $conf = [])
3087  {
3088  return $this->wrap($content, $conf['outerWrap']);
3089  }
3090 
3098  public function stdWrap_insertData($content = '')
3099  {
3100  return $this->insertData($content);
3101  }
3102 
3111  public function stdWrap_postUserFunc($content = '', $conf = [])
3112  {
3113  return $this->callUserFunction($conf['postUserFunc'], $conf['postUserFunc.'], $content);
3114  }
3115 
3125  public function stdWrap_postUserFuncInt($content = '', $conf = [])
3126  {
3127  $substKey = 'INT_SCRIPT.' . $this->getTypoScriptFrontendController()->uniqueHash();
3128  $this->getTypoScriptFrontendController()->config['INTincScript'][$substKey] = [
3129  'content' => $content,
3130  'postUserFunc' => $conf['postUserFuncInt'],
3131  'conf' => $conf['postUserFuncInt.'],
3132  'type' => 'POSTUSERFUNC',
3133  'cObj' => serialize($this)
3134  ];
3135  $content = '<!--' . $substKey . '-->';
3136  return $content;
3137  }
3138 
3147  public function stdWrap_prefixComment($content = '', $conf = [])
3148  {
3149  if (!$this->getTypoScriptFrontendController()->config['config']['disablePrefixComment'] && !empty($conf['prefixComment'])) {
3150  $content = $this->prefixComment($conf['prefixComment'], [], $content);
3151  }
3152  return $content;
3153  }
3154 
3163  public function stdWrap_editIcons($content = '', $conf = [])
3164  {
3165  if ($this->getTypoScriptFrontendController()->beUserLogin && $conf['editIcons']) {
3166  if (!is_array($conf['editIcons.'])) {
3167  $conf['editIcons.'] = [];
3168  }
3169  $content = $this->editIcons($content, $conf['editIcons'], $conf['editIcons.']);
3170  }
3171  return $content;
3172  }
3173 
3182  public function stdWrap_editPanel($content = '', $conf = [])
3183  {
3184  if ($this->getTypoScriptFrontendController()->beUserLogin) {
3185  $content = $this->editPanel($content, $conf['editPanel.']);
3186  }
3187  return $content;
3188  }
3189 
3197  public function stdWrap_cacheStore($content = '', $conf = [])
3198  {
3199  if (!isset($conf['cache.'])) {
3200  return $content;
3201  }
3202  $key = $this->calculateCacheKey($conf['cache.']);
3203  if (empty($key)) {
3204  return $content;
3205  }
3207  $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash');
3208  $tags = $this->calculateCacheTags($conf['cache.']);
3209  $lifetime = $this->calculateCacheLifetime($conf['cache.']);
3210  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap_cacheStore'])) {
3211  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap_cacheStore'] as $_funcRef) {
3212  $params = [
3213  'key' => $key,
3214  'content' => $content,
3215  'lifetime' => $lifetime,
3216  'tags' => $tags
3217  ];
3218  GeneralUtility::callUserFunction($_funcRef, $params, $this);
3219  }
3220  }
3221  $cacheFrontend->set($key, $content, $tags, $lifetime);
3222  return $content;
3223  }
3224 
3234  public function stdWrap_stdWrapPostProcess($content = '', $conf = [])
3235  {
3236  foreach ($this->stdWrapHookObjects as $hookObject) {
3238  $content = $hookObject->stdWrapPostProcess($content, $conf, $this);
3239  }
3240  return $content;
3241  }
3242 
3250  public function stdWrap_debug($content = '')
3251  {
3252  return '<pre>' . htmlspecialchars($content) . '</pre>';
3253  }
3254 
3263  public function stdWrap_debugFunc($content = '', $conf = [])
3264  {
3265  debug((int)$conf['debugFunc'] === 2 ? [$content] : $content);
3266  return $content;
3267  }
3268 
3276  public function stdWrap_debugData($content = '')
3277  {
3278  debug($this->data, '$cObj->data:');
3279  if (is_array($this->alternativeData)) {
3280  debug($this->alternativeData, '$this->alternativeData');
3281  }
3282  return $content;
3283  }
3284 
3294  public function numRows($conf)
3295  {
3296  $conf['select.']['selectFields'] = 'count(*)';
3297  $statement = $this->exec_getQuery($conf['table'], $conf['select.']);
3298 
3299  return (int)$statement->fetchColumn(0);
3300  }
3301 
3310  public function listNum($content, $listNum, $char)
3311  {
3312  $char = $char ?: ',';
3314  $char = chr($char);
3315  }
3316  $temp = explode($char, $content);
3317  $last = '' . (count($temp) - 1);
3318  // Take a random item if requested
3319  if ($listNum === 'rand') {
3320  $listNum = rand(0, count($temp) - 1);
3321  }
3322  $index = $this->calc(str_ireplace('last', $last, $listNum));
3323  return $temp[$index];
3324  }
3325 
3334  public function checkIf($conf)
3335  {
3336  if (!is_array($conf)) {
3337  return true;
3338  }
3339  if (isset($conf['directReturn'])) {
3340  return (bool)$conf['directReturn'];
3341  }
3342  $flag = true;
3343  if (isset($conf['isNull.'])) {
3344  $isNull = $this->stdWrap('', $conf['isNull.']);
3345  if ($isNull !== null) {
3346  $flag = false;
3347  }
3348  }
3349  if (isset($conf['isTrue']) || isset($conf['isTrue.'])) {
3350  $isTrue = isset($conf['isTrue.']) ? trim($this->stdWrap($conf['isTrue'], $conf['isTrue.'])) : trim($conf['isTrue']);
3351  if (!$isTrue) {
3352  $flag = false;
3353  }
3354  }
3355  if (isset($conf['isFalse']) || isset($conf['isFalse.'])) {
3356  $isFalse = isset($conf['isFalse.']) ? trim($this->stdWrap($conf['isFalse'], $conf['isFalse.'])) : trim($conf['isFalse']);
3357  if ($isFalse) {
3358  $flag = false;
3359  }
3360  }
3361  if (isset($conf['isPositive']) || isset($conf['isPositive.'])) {
3362  $number = isset($conf['isPositive.']) ? $this->calc($this->stdWrap($conf['isPositive'], $conf['isPositive.'])) : $this->calc($conf['isPositive']);
3363  if ($number < 1) {
3364  $flag = false;
3365  }
3366  }
3367  if ($flag) {
3368  $value = isset($conf['value.']) ? trim($this->stdWrap($conf['value'], $conf['value.'])) : trim($conf['value']);
3369  if (isset($conf['isGreaterThan']) || isset($conf['isGreaterThan.'])) {
3370  $number = isset($conf['isGreaterThan.']) ? trim($this->stdWrap($conf['isGreaterThan'], $conf['isGreaterThan.'])) : trim($conf['isGreaterThan']);
3371  if ($number <= $value) {
3372  $flag = false;
3373  }
3374  }
3375  if (isset($conf['isLessThan']) || isset($conf['isLessThan.'])) {
3376  $number = isset($conf['isLessThan.']) ? trim($this->stdWrap($conf['isLessThan'], $conf['isLessThan.'])) : trim($conf['isLessThan']);
3377  if ($number >= $value) {
3378  $flag = false;
3379  }
3380  }
3381  if (isset($conf['equals']) || isset($conf['equals.'])) {
3382  $number = isset($conf['equals.']) ? trim($this->stdWrap($conf['equals'], $conf['equals.'])) : trim($conf['equals']);
3383  if ($number != $value) {
3384  $flag = false;
3385  }
3386  }
3387  if (isset($conf['isInList']) || isset($conf['isInList.'])) {
3388  $number = isset($conf['isInList.']) ? trim($this->stdWrap($conf['isInList'], $conf['isInList.'])) : trim($conf['isInList']);
3389  if (!GeneralUtility::inList($value, $number)) {
3390  $flag = false;
3391  }
3392  }
3393  }
3394  if ($conf['negate']) {
3395  $flag = !$flag;
3396  }
3397  return $flag;
3398  }
3399 
3409  public function filelist($data)
3410  {
3411  $data = trim($data);
3412  if ($data === '') {
3413  return '';
3414  }
3415  $data_arr = explode('|', $data);
3416  // read directory:
3417  // MUST exist!
3418  $path = '';
3419  if ($this->getTypoScriptFrontendController()->lockFilePath) {
3420  // Cleaning name..., only relative paths accepted.
3421  $path = $this->clean_directory($data_arr[0]);
3422  // See if path starts with lockFilePath, the additional '/' is needed because clean_directory gets rid of it
3423  $path = GeneralUtility::isFirstPartOfStr($path . '/', $this->getTypoScriptFrontendController()->lockFilePath) ? $path : '';
3424  }
3425  if (!$path) {
3426  return '';
3427  }
3428  $items = [
3429  'files' => [],
3430  'sorting' => []
3431  ];
3432  $ext_list = strtolower(GeneralUtility::uniqueList($data_arr[1]));
3433  $sorting = trim($data_arr[2]);
3434  // Read dir:
3435  $d = @dir($path);
3436  if (is_object($d)) {
3437  $count = 0;
3438  while ($entry = $d->read()) {
3439  if ($entry !== '.' && $entry !== '..') {
3440  // Because of odd PHP-error where <br />-tag is sometimes placed after a filename!!
3441  $wholePath = $path . '/' . $entry;
3442  if (file_exists($wholePath) && filetype($wholePath) === 'file') {
3443  $info = GeneralUtility::split_fileref($wholePath);
3444  if (!$ext_list || GeneralUtility::inList($ext_list, $info['fileext'])) {
3445  $items['files'][] = $info['file'];
3446  switch ($sorting) {
3447  case 'name':
3448  $items['sorting'][] = strtolower($info['file']);
3449  break;
3450  case 'size':
3451  $items['sorting'][] = filesize($wholePath);
3452  break;
3453  case 'ext':
3454  $items['sorting'][] = $info['fileext'];
3455  break;
3456  case 'date':
3457  $items['sorting'][] = filectime($wholePath);
3458  break;
3459  case 'mdate':
3460  $items['sorting'][] = filemtime($wholePath);
3461  break;
3462  default:
3463  $items['sorting'][] = $count;
3464  }
3465  $count++;
3466  }
3467  }
3468  }
3469  }
3470  $d->close();
3471  }
3472  // Sort if required
3473  if (!empty($items['sorting'])) {
3474  if (strtolower(trim($data_arr[3])) !== 'r') {
3475  asort($items['sorting']);
3476  } else {
3477  arsort($items['sorting']);
3478  }
3479  }
3480  if (!empty($items['files'])) {
3481  // Make list
3482  reset($items['sorting']);
3483  $fullPath = trim($data_arr[4]);
3484  $list_arr = [];
3485  foreach ($items['sorting'] as $key => $v) {
3486  $list_arr[] = $fullPath ? $path . '/' . $items['files'][$key] : $items['files'][$key];
3487  }
3488  return implode(',', $list_arr);
3489  }
3490  return '';
3491  }
3492 
3501  public function clean_directory($theDir)
3502  {
3503  // proceeds if no '//', '..' or '\' is in the $theFile
3504  if (GeneralUtility::validPathStr($theDir)) {
3505  // Removes all dots, slashes and spaces after a path...
3506  $theDir = preg_replace('/[\\/\\. ]*$/', '', $theDir);
3507  if (!GeneralUtility::isAbsPath($theDir) && @is_dir($theDir)) {
3508  return $theDir;
3509  }
3510  }
3511  return '';
3512  }
3513 
3524  public function HTMLparser_TSbridge($theValue, $conf)
3525  {
3526  $htmlParser = GeneralUtility::makeInstance(HtmlParser::class);
3527  $htmlParserCfg = $htmlParser->HTMLparserConfig($conf);
3528  return $htmlParser->HTMLcleaner($theValue, $htmlParserCfg[0], $htmlParserCfg[1], $htmlParserCfg[2], $htmlParserCfg[3]);
3529  }
3530 
3539  public function dataWrap($content, $wrap)
3540  {
3541  return $this->wrap($content, $this->insertData($wrap));
3542  }
3543 
3557  public function insertData($str)
3558  {
3559  $inside = 0;
3560  $newVal = '';
3561  $pointer = 0;
3562  $totalLen = strlen($str);
3563  do {
3564  if (!$inside) {
3565  $len = strcspn(substr($str, $pointer), '{');
3566  $newVal .= substr($str, $pointer, $len);
3567  $inside = true;
3568  if (substr($str, $pointer + $len + 1, 1) === '#') {
3569  $len2 = strcspn(substr($str, $pointer + $len), '}');
3570  $newVal .= substr($str, $pointer + $len, $len2);
3571  $len += $len2;
3572  $inside = false;
3573  }
3574  } else {
3575  $len = strcspn(substr($str, $pointer), '}') + 1;
3576  $newVal .= $this->getData(substr($str, $pointer + 1, $len - 2), $this->data);
3577  $inside = false;
3578  }
3579  $pointer += $len;
3580  } while ($pointer < $totalLen);
3581  return $newVal;
3582  }
3583 
3594  public function prefixComment($str, $conf, $content)
3595  {
3596  if (empty($str)) {
3597  return $content;
3598  }
3599  $parts = explode('|', $str);
3600  $indent = (int)$parts[0];
3601  $comment = htmlspecialchars($this->insertData($parts[1]));
3602  $output = LF
3603  . str_pad('', $indent, TAB) . '<!-- ' . $comment . ' [begin] -->' . LF
3604  . str_pad('', ($indent + 1), TAB) . $content . LF
3605  . str_pad('', $indent, TAB) . '<!-- ' . $comment . ' [end] -->' . LF
3606  . str_pad('', ($indent + 1), TAB);
3607  return $output;
3608  }
3609 
3619  public function substring($content, $options)
3620  {
3621  $options = GeneralUtility::intExplode(',', $options . ',');
3622  if ($options[1]) {
3623  return mb_substr($content, $options[0], $options[1], 'utf-8');
3624  }
3625  return mb_substr($content, $options[0], null, 'utf-8');
3626  }
3627 
3637  public function crop($content, $options)
3638  {
3639  $options = explode('|', $options);
3640  $chars = (int)$options[0];
3641  $afterstring = trim($options[1]);
3642  $crop2space = trim($options[2]);
3643  if ($chars) {
3644  if (mb_strlen($content, 'utf-8') > abs($chars)) {
3645  $truncatePosition = false;
3646  if ($chars < 0) {
3647  $content = mb_substr($content, $chars, null, 'utf-8');
3648  if ($crop2space) {
3649  $truncatePosition = strpos($content, ' ');
3650  }
3651  $content = $truncatePosition ? $afterstring . substr($content, $truncatePosition) : $afterstring . $content;
3652  } else {
3653  $content = mb_substr($content, 0, $chars, 'utf-8');
3654  if ($crop2space) {
3655  $truncatePosition = strrpos($content, ' ');
3656  }
3657  $content = $truncatePosition ? substr($content, 0, $truncatePosition) . $afterstring : $content . $afterstring;
3658  }
3659  }
3660  }
3661  return $content;
3662  }
3663 
3677  public function cropHTML($content, $options)
3678  {
3679  $options = explode('|', $options);
3680  $chars = (int)$options[0];
3681  $absChars = abs($chars);
3682  $replacementForEllipsis = trim($options[1]);
3683  $crop2space = trim($options[2]) === '1';
3684  // Split $content into an array(even items in the array are outside the tags, odd numbers are tag-blocks).
3685  $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';
3686  $tagsRegEx = '
3687  (
3688  (?:
3689  <!--.*?--> # a comment
3690  |
3691  <canvas[^>]*>.*?</canvas> # a canvas tag
3692  |
3693  <script[^>]*>.*?</script> # a script tag
3694  |
3695  <noscript[^>]*>.*?</noscript> # a noscript tag
3696  |
3697  <template[^>]*>.*?</template> # a template tag
3698  )
3699  |
3700  </?(?:' . $tags . ')+ # opening tag (\'<tag\') or closing tag (\'</tag\')
3701  (?:
3702  (?:
3703  (?:
3704  \\s+\\w[\\w-]* # EITHER spaces, followed by attribute names
3705  (?:
3706  \\s*=?\\s* # equals
3707  (?>
3708  ".*?" # attribute values in double-quotes
3709  |
3710  \'.*?\' # attribute values in single-quotes
3711  |
3712  [^\'">\\s]+ # plain attribute values
3713  )
3714  )?
3715  )
3716  | # OR a single dash (for TYPO3 link tag)
3717  (?:
3718  \\s+-
3719  )
3720  )+\\s*
3721  | # OR only spaces
3722  \\s*
3723  )
3724  /?> # closing the tag with \'>\' or \'/>\'
3725  )';
3726  $splittedContent = preg_split('%' . $tagsRegEx . '%xs', $content, -1, PREG_SPLIT_DELIM_CAPTURE);
3727  // Reverse array if we are cropping from right.
3728  if ($chars < 0) {
3729  $splittedContent = array_reverse($splittedContent);
3730  }
3731  // Crop the text (chars of tag-blocks are not counted).
3732  $strLen = 0;
3733  // This is the offset of the content item which was cropped.
3734  $croppedOffset = null;
3735  $countSplittedContent = count($splittedContent);
3736  for ($offset = 0; $offset < $countSplittedContent; $offset++) {
3737  if ($offset % 2 === 0) {
3738  $tempContent = $splittedContent[$offset];
3739  $thisStrLen = mb_strlen(html_entity_decode($tempContent, ENT_COMPAT, 'UTF-8'), 'utf-8');
3740  if ($strLen + $thisStrLen > $absChars) {
3741  $croppedOffset = $offset;
3742  $cropPosition = $absChars - $strLen;
3743  // The snippet "&[^&\s;]{2,8};" in the RegEx below represents entities.
3744  $patternMatchEntityAsSingleChar = '(&[^&\\s;]{2,8};|.)';
3745  $cropRegEx = $chars < 0 ? '#' . $patternMatchEntityAsSingleChar . '{0,' . ($cropPosition + 1) . '}$#uis' : '#^' . $patternMatchEntityAsSingleChar . '{0,' . ($cropPosition + 1) . '}#uis';
3746  if (preg_match($cropRegEx, $tempContent, $croppedMatch)) {
3747  $tempContentPlusOneCharacter = $croppedMatch[0];
3748  } else {
3749  $tempContentPlusOneCharacter = false;
3750  }
3751  $cropRegEx = $chars < 0 ? '#' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}$#uis' : '#^' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}#uis';
3752  if (preg_match($cropRegEx, $tempContent, $croppedMatch)) {
3753  $tempContent = $croppedMatch[0];
3754  if ($crop2space && $tempContentPlusOneCharacter !== false) {
3755  $cropRegEx = $chars < 0 ? '#(?<=\\s)' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}$#uis' : '#^' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}(?=\\s)#uis';
3756  if (preg_match($cropRegEx, $tempContentPlusOneCharacter, $croppedMatch)) {
3757  $tempContent = $croppedMatch[0];
3758  }
3759  }
3760  }
3761  $splittedContent[$offset] = $tempContent;
3762  break;
3763  }
3764  $strLen += $thisStrLen;
3765  }
3766  }
3767  // Close cropped tags.
3768  $closingTags = [];
3769  if ($croppedOffset !== null) {
3770  $openingTagRegEx = '#^<(\\w+)(?:\\s|>)#';
3771  $closingTagRegEx = '#^</(\\w+)(?:\\s|>)#';
3772  for ($offset = $croppedOffset - 1; $offset >= 0; $offset = $offset - 2) {
3773  if (substr($splittedContent[$offset], -2) === '/>') {
3774  // Ignore empty element tags (e.g. <br />).
3775  continue;
3776  }
3777  preg_match($chars < 0 ? $closingTagRegEx : $openingTagRegEx, $splittedContent[$offset], $matches);
3778  $tagName = isset($matches[1]) ? $matches[1] : null;
3779  if ($tagName !== null) {
3780  // Seek for the closing (or opening) tag.
3781  $countSplittedContent = count($splittedContent);
3782  for ($seekingOffset = $offset + 2; $seekingOffset < $countSplittedContent; $seekingOffset = $seekingOffset + 2) {
3783  preg_match($chars < 0 ? $openingTagRegEx : $closingTagRegEx, $splittedContent[$seekingOffset], $matches);
3784  $seekingTagName = isset($matches[1]) ? $matches[1] : null;
3785  if ($tagName === $seekingTagName) {
3786  // We found a matching tag.
3787  // Add closing tag only if it occurs after the cropped content item.
3788  if ($seekingOffset > $croppedOffset) {
3789  $closingTags[] = $splittedContent[$seekingOffset];
3790  }
3791  break;
3792  }
3793  }
3794  }
3795  }
3796  // Drop the cropped items of the content array. The $closingTags will be added later on again.
3797  array_splice($splittedContent, $croppedOffset + 1);
3798  }
3799  $splittedContent = array_merge($splittedContent, [
3800  $croppedOffset !== null ? $replacementForEllipsis : ''
3801  ], $closingTags);
3802  // Reverse array once again if we are cropping from the end.
3803  if ($chars < 0) {
3804  $splittedContent = array_reverse($splittedContent);
3805  }
3806  return implode('', $splittedContent);
3807  }
3808 
3818  public function removeBadHTML($text)
3819  {
3821  // Copyright 2002-2003 Thomas Bley
3822  $text = preg_replace([
3823  '\'<script[^>]*?>.*?</script[^>]*?>\'si',
3824  '\'<applet[^>]*?>.*?</applet[^>]*?>\'si',
3825  '\'<object[^>]*?>.*?</object[^>]*?>\'si',
3826  '\'<iframe[^>]*?>.*?</iframe[^>]*?>\'si',
3827  '\'<frameset[^>]*?>.*?</frameset[^>]*?>\'si',
3828  '\'<style[^>]*?>.*?</style[^>]*?>\'si',
3829  '\'<marquee[^>]*?>.*?</marquee[^>]*?>\'si',
3830  '\'<script[^>]*?>\'si',
3831  '\'<meta[^>]*?>\'si',
3832  '\'<base[^>]*?>\'si',
3833  '\'<applet[^>]*?>\'si',
3834  '\'<object[^>]*?>\'si',
3835  '\'<link[^>]*?>\'si',
3836  '\'<iframe[^>]*?>\'si',
3837  '\'<frame[^>]*?>\'si',
3838  '\'<frameset[^>]*?>\'si',
3839  '\'<input[^>]*?>\'si',
3840  '\'<form[^>]*?>\'si',
3841  '\'<embed[^>]*?>\'si',
3842  '\'background-image:url\'si',
3843  '\'<\\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'
3844  ], '', $text);
3845  $text = preg_replace('/<a[^>]*href[[:space:]]*=[[:space:]]*["\']?[[:space:]]*javascript[^>]*/i', '', $text);
3846  // Return clean content
3847  return $text;
3848  }
3849 
3858  public function addParams($content, $conf)
3859  {
3860  // For XHTML compliance.
3861  $lowerCaseAttributes = true;
3862  if (!is_array($conf)) {
3863  return $content;
3864  }
3865  $key = 1;
3866  $parts = explode('<', $content);
3867  if ((int)$conf['_offset']) {
3868  $key = (int)$conf['_offset'] < 0 ? count($parts) + (int)$conf['_offset'] : (int)$conf['_offset'];
3869  }
3870  $subparts = explode('>', $parts[$key]);
3871  if (trim($subparts[0])) {
3872  // Get attributes and name
3873  $attribs = GeneralUtility::get_tag_attributes('<' . $subparts[0] . '>');
3874  list($tagName) = explode(' ', $subparts[0], 2);
3875  // adds/overrides attributes
3876  foreach ($conf as $pkey => $val) {
3877  if (substr($pkey, -1) !== '.' && $pkey[0] !== '_') {
3878  $tmpVal = isset($conf[$pkey . '.']) ? $this->stdWrap($conf[$pkey], $conf[$pkey . '.']) : (string)$val;
3879  if ($lowerCaseAttributes) {
3880  $pkey = strtolower($pkey);
3881  }
3882  if ($tmpVal !== '') {
3883  $attribs[$pkey] = $tmpVal;
3884  }
3885  }
3886  }
3887  // Re-assembles the tag and content
3888  $subparts[0] = trim($tagName . ' ' . GeneralUtility::implodeAttributes($attribs));
3889  $parts[$key] = implode('>', $subparts);
3890  $content = implode('<', $parts);
3891  }
3892  return $content;
3893  }
3894 
3905  public function filelink($theValue, $conf)
3906  {
3907  $conf['path'] = isset($conf['path.']) ? $this->stdWrap($conf['path'], $conf['path.']) : $conf['path'];
3908  $theFile = trim($conf['path']) . $theValue;
3909  if (!@is_file($theFile)) {
3910  return '';
3911  }
3912  $theFileEnc = str_replace('%2F', '/', rawurlencode($theFile));
3913  $title = $conf['title'];
3914  if (isset($conf['title.'])) {
3915  $title = $this->stdWrap($title, $conf['title.']);
3916  }
3917  $target = $conf['target'];
3918  if (isset($conf['target.'])) {
3919  $target = $this->stdWrap($target, $conf['target.']);
3920  }
3921  $tsfe = $this->getTypoScriptFrontendController();
3922 
3923  $typoLinkConf = [
3924  'parameter' => $theFileEnc,
3925  'fileTarget' => $target,
3926  'title' => $title,
3927  'ATagParams' => $this->getATagParams($conf)
3928  ];
3929 
3930  if (isset($conf['typolinkConfiguration.'])) {
3931  $additionalTypoLinkConfiguration = $conf['typolinkConfiguration.'];
3932  // We only allow additional configuration. This is why the generated conf overwrites the additional conf.
3933  ArrayUtility::mergeRecursiveWithOverrule($additionalTypoLinkConfiguration, $typoLinkConf);
3934  $typoLinkConf = $additionalTypoLinkConfiguration;
3935  }
3936 
3937  $theLinkWrap = $this->typoLink('|', $typoLinkConf);
3938  $theSize = filesize($theFile);
3939  $fI = GeneralUtility::split_fileref($theFile);
3940  $icon = '';
3941  if ($conf['icon']) {
3942  $conf['icon.']['path'] = isset($conf['icon.']['path.'])
3943  ? $this->stdWrap($conf['icon.']['path'], $conf['icon.']['path.'])
3944  : $conf['icon.']['path'];
3945  $iconP = !empty($conf['icon.']['path'])
3946  ? $conf['icon.']['path']
3947  : ExtensionManagementUtility::siteRelPath('frontend') . 'Resources/Public/Icons/FileIcons/';
3948  $conf['icon.']['ext'] = isset($conf['icon.']['ext.'])
3949  ? $this->stdWrap($conf['icon.']['ext'], $conf['icon.']['ext.'])
3950  : $conf['icon.']['ext'];
3951  $iconExt = !empty($conf['icon.']['ext']) ? '.' . $conf['icon.']['ext'] : '.gif';
3952  $icon = @is_file(($iconP . $fI['fileext'] . $iconExt))
3953  ? $iconP . $fI['fileext'] . $iconExt
3954  : $iconP . 'default' . $iconExt;
3955  // Checking for images: If image, then return link to thumbnail.
3956  $IEList = isset($conf['icon_image_ext_list.']) ? $this->stdWrap($conf['icon_image_ext_list'], $conf['icon_image_ext_list.']) : $conf['icon_image_ext_list'];
3957  $image_ext_list = str_replace(' ', '', strtolower($IEList));
3958  if ($fI['fileext'] && GeneralUtility::inList($image_ext_list, $fI['fileext'])) {
3959  if ($conf['iconCObject']) {
3960  $icon = $this->cObjGetSingle($conf['iconCObject'], $conf['iconCObject.'], 'iconCObject');
3961  } else {
3962  $notFoundThumb = ExtensionManagementUtility::siteRelPath('core') . 'Resources/Public/Images/NotFound.gif';
3963  $sizeParts = [64, 64];
3964  if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['thumbnails']) {
3965  // using the File Abstraction Layer to generate a preview image
3966  try {
3968  $fileObject = ResourceFactory::getInstance()->retrieveFileOrFolderObject($theFile);
3969  if ($fileObject->isMissing()) {
3970  $icon = $notFoundThumb;
3971  } else {
3972  $fileExtension = $fileObject->getExtension();
3973  if ($fileExtension === 'ttf' || GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], $fileExtension)) {
3974  if ($conf['icon_thumbSize'] || $conf['icon_thumbSize.']) {
3975  $thumbSize = isset($conf['icon_thumbSize.']) ? $this->stdWrap($conf['icon_thumbSize'], $conf['icon_thumbSize.']) : $conf['icon_thumbSize'];
3976  $sizeParts = explode('x', $thumbSize);
3977  }
3978  $icon = $fileObject->process(ProcessedFile::CONTEXT_IMAGEPREVIEW, [
3979  'width' => $sizeParts[0],
3980  'height' => $sizeParts[1]
3981  ])->getPublicUrl(true);
3982  }
3983  }
3984  } catch (ResourceDoesNotExistException $exception) {
3985  $icon = $notFoundThumb;
3986  }
3987  } else {
3988  $icon = $notFoundThumb;
3989  }
3990  $urlPrefix = '';
3991  if (parse_url($icon, PHP_URL_HOST) === null) {
3992  $urlPrefix = $tsfe->absRefPrefix;
3993  }
3994  $icon = '<img src="' . htmlspecialchars($urlPrefix . $icon) . '"' .
3995  ' width="' . (int)$sizeParts[0] . '" height="' . (int)$sizeParts[1] . '" ' .
3996  $this->getBorderAttr(' border="0"') . '' . $this->getAltParam($conf) . ' />';
3997  }
3998  } else {
3999  $conf['icon.']['widthAttribute'] = isset($conf['icon.']['widthAttribute.'])
4000  ? $this->stdWrap($conf['icon.']['widthAttribute'], $conf['icon.']['widthAttribute.'])
4001  : $conf['icon.']['widthAttribute'];
4002  $iconWidth = !empty($conf['icon.']['widthAttribute']) ? $conf['icon.']['widthAttribute'] : 18;
4003  $conf['icon.']['heightAttribute'] = isset($conf['icon.']['heightAttribute.'])
4004  ? $this->stdWrap($conf['icon.']['heightAttribute'], $conf['icon.']['heightAttribute.'])
4005  : $conf['icon.']['heightAttribute'];
4006  $iconHeight = !empty($conf['icon.']['heightAttribute']) ? (int)$conf['icon.']['heightAttribute'] : 16;
4007  $icon = '<img src="' . htmlspecialchars($tsfe->absRefPrefix . $icon) . '" width="' . (int)$iconWidth . '" height="' . (int)$iconHeight . '"'
4008  . $this->getBorderAttr(' border="0"') . $this->getAltParam($conf) . ' />';
4009  }
4010  if ($conf['icon_link'] && !$conf['combinedLink']) {
4011  $icon = $this->wrap($icon, $theLinkWrap);
4012  }
4013  $icon = isset($conf['icon.']) ? $this->stdWrap($icon, $conf['icon.']) : $icon;
4014  }
4015  $size = '';
4016  if ($conf['size']) {
4017  $size = isset($conf['size.']) ? $this->stdWrap($theSize, $conf['size.']) : $theSize;
4018  }
4019  // Wrapping file label
4020  if ($conf['removePrependedNumbers']) {
4021  $theValue = preg_replace('/_[0-9][0-9](\\.[[:alnum:]]*)$/', '\\1', $theValue);
4022  }
4023  if (isset($conf['labelStdWrap.'])) {
4024  $theValue = $this->stdWrap($theValue, $conf['labelStdWrap.']);
4025  }
4026  // Wrapping file
4027  $wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
4028  if ($conf['combinedLink']) {
4029  $theValue = $icon . $theValue;
4030  if ($conf['ATagBeforeWrap']) {
4031  $theValue = $this->wrap($this->wrap($theValue, $wrap), $theLinkWrap);
4032  } else {
4033  $theValue = $this->wrap($this->wrap($theValue, $theLinkWrap), $wrap);
4034  }
4035  $file = isset($conf['file.']) ? $this->stdWrap($theValue, $conf['file.']) : $theValue;
4036  // output
4037  $output = $file . $size;
4038  } else {
4039  if ($conf['ATagBeforeWrap']) {
4040  $theValue = $this->wrap($this->wrap($theValue, $wrap), $theLinkWrap);
4041  } else {
4042  $theValue = $this->wrap($this->wrap($theValue, $theLinkWrap), $wrap);
4043  }
4044  $file = isset($conf['file.']) ? $this->stdWrap($theValue, $conf['file.']) : $theValue;
4045  // output
4046  $output = $icon . $file . $size;
4047  }
4048  if (isset($conf['stdWrap.'])) {
4049  $output = $this->stdWrap($output, $conf['stdWrap.']);
4050  }
4051  return $output;
4052  }
4053 
4061  public function calc($val)
4062  {
4063  $parts = GeneralUtility::splitCalc($val, '+-*/');
4064  $value = 0;
4065  foreach ($parts as $part) {
4066  $theVal = $part[1];
4067  $sign = $part[0];
4068  if ((string)(int)$theVal === (string)$theVal) {
4069  $theVal = (int)$theVal;
4070  } else {
4071  $theVal = 0;
4072  }
4073  if ($sign === '-') {
4074  $value -= $theVal;
4075  }
4076  if ($sign === '+') {
4077  $value += $theVal;
4078  }
4079  if ($sign === '/') {
4080  if ((int)$theVal) {
4081  $value /= (int)$theVal;
4082  }
4083  }
4084  if ($sign === '*') {
4085  $value *= $theVal;
4086  }
4087  }
4088  return $value;
4089  }
4090 
4100  public function calcIntExplode($delim, $string)
4101  {
4102  $temp = explode($delim, $string);
4103  foreach ($temp as $key => $val) {
4104  $temp[$key] = (int)$this->calc($val);
4105  }
4106  return $temp;
4107  }
4108 
4120  public function splitObj($value, $conf)
4121  {
4122  $conf['token'] = isset($conf['token.']) ? $this->stdWrap($conf['token'], $conf['token.']) : $conf['token'];
4123  if ($conf['token'] === '') {
4124  return $value;
4125  }
4126  $valArr = explode($conf['token'], $value);
4127 
4128  // return value directly by returnKey. No further processing
4129  if (!empty($valArr) && (MathUtility::canBeInterpretedAsInteger($conf['returnKey']) || $conf['returnKey.'])) {
4130  $key = isset($conf['returnKey.']) ? (int)$this->stdWrap($conf['returnKey'], $conf['returnKey.']) : (int)$conf['returnKey'];
4131  return isset($valArr[$key]) ? $valArr[$key] : '';
4132  }
4133 
4134  // return the amount of elements. No further processing
4135  if (!empty($valArr) && ($conf['returnCount'] || $conf['returnCount.'])) {
4136  $returnCount = isset($conf['returnCount.']) ? (bool)$this->stdWrap($conf['returnCount'], $conf['returnCount.']) : (bool)$conf['returnCount'];
4137  return $returnCount ? count($valArr) : 0;
4138  }
4139 
4140  // calculate splitCount
4141  $splitCount = count($valArr);
4142  $max = isset($conf['max.']) ? (int)$this->stdWrap($conf['max'], $conf['max.']) : (int)$conf['max'];
4143  if ($max && $splitCount > $max) {
4144  $splitCount = $max;
4145  }
4146  $min = isset($conf['min.']) ? (int)$this->stdWrap($conf['min'], $conf['min.']) : (int)$conf['min'];
4147  if ($min && $splitCount < $min) {
4148  $splitCount = $min;
4149  }
4150  $wrap = isset($conf['wrap.']) ? (string)$this->stdWrap($conf['wrap'], $conf['wrap.']) : (string)$conf['wrap'];
4151  $cObjNumSplitConf = isset($conf['cObjNum.']) ? (string)$this->stdWrap($conf['cObjNum'], $conf['cObjNum.']) : (string)$conf['cObjNum'];
4152  $splitArr = [];
4153  if ($wrap !== '' || $cObjNumSplitConf !== '') {
4154  $splitArr['wrap'] = $wrap;
4155  $splitArr['cObjNum'] = $cObjNumSplitConf;
4156  $splitArr = GeneralUtility::makeInstance(TypoScriptService::class)
4157  ->explodeConfigurationForOptionSplit($splitArr, $splitCount);
4158  }
4159  $content = '';
4160  for ($a = 0; $a < $splitCount; $a++) {
4161  $this->getTypoScriptFrontendController()->register['SPLIT_COUNT'] = $a;
4162  $value = '' . $valArr[$a];
4163  $this->data[$this->currentValKey] = $value;
4164  if ($splitArr[$a]['cObjNum']) {
4165  $objName = (int)$splitArr[$a]['cObjNum'];
4166  $value = isset($conf[$objName . '.'])
4167  ? $this->stdWrap($this->cObjGet($conf[$objName . '.'], $objName . '.'), $conf[$objName . '.'])
4168  : $this->cObjGet($conf[$objName . '.'], $objName . '.');
4169  }
4170  $wrap = isset($splitArr[$a]['wrap.']) ? $this->stdWrap($splitArr[$a]['wrap'], $splitArr[$a]['wrap.']) : $splitArr[$a]['wrap'];
4171  if ($wrap) {
4172  $value = $this->wrap($value, $wrap);
4173  }
4174  $content .= $value;
4175  }
4176  return $content;
4177  }
4178 
4186  protected function replacement($content, array $configuration)
4187  {
4188  // Sorts actions in configuration by numeric index
4189  ksort($configuration, SORT_NUMERIC);
4190  foreach ($configuration as $index => $action) {
4191  // Checks whether we have an valid action and a numeric key ending with a dot ("10.")
4192  if (is_array($action) && substr($index, -1) === '.' && MathUtility::canBeInterpretedAsInteger(substr($index, 0, -1))) {
4193  $content = $this->replacementSingle($content, $action);
4194  }
4195  }
4196  return $content;
4197  }
4198 
4206  protected function replacementSingle($content, array $configuration)
4207  {
4208  if ((isset($configuration['search']) || isset($configuration['search.'])) && (isset($configuration['replace']) || isset($configuration['replace.']))) {
4209  // Gets the strings
4210  $search = isset($configuration['search.']) ? $this->stdWrap($configuration['search'], $configuration['search.']) : $configuration['search'];
4211  $replace = isset($configuration['replace.']) ? $this->stdWrap($configuration['replace'], $configuration['replace.']) : $configuration['replace'];
4212  // Determines whether regular expression shall be used
4213  if (isset($configuration['useRegExp']) || $configuration['useRegExp.']) {
4214  $useRegularExpression = isset($configuration['useRegExp.']) ? $this->stdWrap($configuration['useRegExp'], $configuration['useRegExp.']) : $configuration['useRegExp'];
4215  }
4216  // Determines whether replace-pattern uses option-split
4217  if (isset($configuration['useOptionSplitReplace']) || isset($configuration['useOptionSplitReplace.'])) {
4218  $useOptionSplitReplace = isset($configuration['useOptionSplitReplace.']) ? $this->stdWrap($configuration['useOptionSplitReplace'], $configuration['useOptionSplitReplace.']) : $configuration['useOptionSplitReplace'];
4219  }
4220 
4221  // Performs a replacement by preg_replace()
4222  if (isset($useRegularExpression)) {
4223  // Get separator-character which precedes the string and separates search-string from the modifiers
4224  $separator = $search[0];
4225  $startModifiers = strrpos($search, $separator);
4226  if ($separator !== false && $startModifiers > 0) {
4227  $modifiers = substr($search, $startModifiers + 1);
4228  // remove "e" (eval-modifier), which would otherwise allow to run arbitrary PHP-code
4229  $modifiers = str_replace('e', '', $modifiers);
4230  $search = substr($search, 0, ($startModifiers + 1)) . $modifiers;
4231  }
4232  if (empty($useOptionSplitReplace)) {
4233  $content = preg_replace($search, $replace, $content);
4234  } else {
4235  // init for replacement
4236  $splitCount = preg_match_all($search, $content, $matches);
4237  $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
4238  $replaceArray = $typoScriptService->explodeConfigurationForOptionSplit([$replace], $splitCount);
4239  $replaceCount = 0;
4240 
4241  $replaceCallback = function ($match) use ($replaceArray, $search, &$replaceCount) {
4242  $replaceCount++;
4243  return preg_replace($search, $replaceArray[$replaceCount - 1][0], $match[0]);
4244  };
4245  $content = preg_replace_callback($search, $replaceCallback, $content);
4246  }
4247  } else {
4248  if (empty($useOptionSplitReplace)) {
4249  $content = str_replace($search, $replace, $content);
4250  } else {
4251  // turn search-string into a preg-pattern
4252  $searchPreg = '#' . preg_quote($search, '#') . '#';
4253 
4254  // init for replacement
4255  $splitCount = preg_match_all($searchPreg, $content, $matches);
4256  $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
4257  $replaceArray = $typoScriptService->explodeConfigurationForOptionSplit([$replace], $splitCount);
4258  $replaceCount = 0;
4259 
4260  $replaceCallback = function () use ($replaceArray, $search, &$replaceCount) {
4261  $replaceCount++;
4262  return $replaceArray[$replaceCount - 1][0];
4263  };
4264  $content = preg_replace_callback($searchPreg, $replaceCallback, $content);
4265  }
4266  }
4267  }
4268  return $content;
4269  }
4270 
4279  protected function round($content, array $conf = [])
4280  {
4281  $decimals = isset($conf['decimals.']) ? $this->stdWrap($conf['decimals'], $conf['decimals.']) : $conf['decimals'];
4282  $type = isset($conf['roundType.']) ? $this->stdWrap($conf['roundType'], $conf['roundType.']) : $conf['roundType'];
4283  $floatVal = (float)$content;
4284  switch ($type) {
4285  case 'ceil':
4286  $content = ceil($floatVal);
4287  break;
4288  case 'floor':
4289  $content = floor($floatVal);
4290  break;
4291  case 'round':
4292 
4293  default:
4294  $content = round($floatVal, (int)$decimals);
4295  }
4296  return $content;
4297  }
4298 
4307  public function numberFormat($content, $conf)
4308  {
4309  $decimals = isset($conf['decimals.']) ? (int)$this->stdWrap($conf['decimals'], $conf['decimals.']) : (int)$conf['decimals'];
4310  $dec_point = isset($conf['dec_point.']) ? $this->stdWrap($conf['dec_point'], $conf['dec_point.']) : $conf['dec_point'];
4311  $thousands_sep = isset($conf['thousands_sep.']) ? $this->stdWrap($conf['thousands_sep'], $conf['thousands_sep.']) : $conf['thousands_sep'];
4312  return number_format((float)$content, $decimals, $dec_point, $thousands_sep);
4313  }
4314 
4334  public function parseFunc($theValue, $conf, $ref = '')
4335  {
4336  // Fetch / merge reference, if any
4337  if ($ref) {
4338  $temp_conf = [
4339  'parseFunc' => $ref,
4340  'parseFunc.' => $conf
4341  ];
4342  $temp_conf = $this->mergeTSRef($temp_conf, 'parseFunc');
4343  $conf = $temp_conf['parseFunc.'];
4344  }
4345  // Process:
4346  if ((string)$conf['externalBlocks'] === '') {
4347  return $this->_parseFunc($theValue, $conf);
4348  }
4349  $tags = strtolower(implode(',', GeneralUtility::trimExplode(',', $conf['externalBlocks'])));
4350  $htmlParser = GeneralUtility::makeInstance(HtmlParser::class);
4351  $parts = $htmlParser->splitIntoBlock($tags, $theValue);
4352  foreach ($parts as $k => $v) {
4353  if ($k % 2) {
4354  // font:
4355  $tagName = strtolower($htmlParser->getFirstTagName($v));
4356  $cfg = $conf['externalBlocks.'][$tagName . '.'];
4357  if ($cfg['stripNLprev'] || $cfg['stripNL']) {
4358  $parts[$k - 1] = preg_replace('/' . CR . '?' . LF . '[ ]*$/', '', $parts[$k - 1]);
4359  }
4360  if ($cfg['stripNLnext'] || $cfg['stripNL']) {
4361  $parts[$k + 1] = preg_replace('/^[ ]*' . CR . '?' . LF . '/', '', $parts[$k + 1]);
4362  }
4363  }
4364  }
4365  foreach ($parts as $k => $v) {
4366  if ($k % 2) {
4367  $tag = $htmlParser->getFirstTag($v);
4368  $tagName = strtolower($htmlParser->getFirstTagName($v));
4369  $cfg = $conf['externalBlocks.'][$tagName . '.'];
4370  if ($cfg['callRecursive']) {
4371  $parts[$k] = $this->parseFunc($htmlParser->removeFirstAndLastTag($v), $conf);
4372  if (!$cfg['callRecursive.']['dontWrapSelf']) {
4373  if ($cfg['callRecursive.']['alternativeWrap']) {
4374  $parts[$k] = $this->wrap($parts[$k], $cfg['callRecursive.']['alternativeWrap']);
4375  } else {
4376  if (is_array($cfg['callRecursive.']['tagStdWrap.'])) {
4377  $tag = $this->stdWrap($tag, $cfg['callRecursive.']['tagStdWrap.']);
4378  }
4379  $parts[$k] = $tag . $parts[$k] . '</' . $tagName . '>';
4380  }
4381  }
4382  } elseif ($cfg['HTMLtableCells']) {
4383  $rowParts = $htmlParser->splitIntoBlock('tr', $parts[$k]);
4384  foreach ($rowParts as $kk => $vv) {
4385  if ($kk % 2) {
4386  $colParts = $htmlParser->splitIntoBlock('td,th', $vv);
4387  $cc = 0;
4388  foreach ($colParts as $kkk => $vvv) {
4389  if ($kkk % 2) {
4390  $cc++;
4391  $tag = $htmlParser->getFirstTag($vvv);
4392  $tagName = strtolower($htmlParser->getFirstTagName($vvv));
4393  $colParts[$kkk] = $htmlParser->removeFirstAndLastTag($vvv);
4394  if ($cfg['HTMLtableCells.'][$cc . '.']['callRecursive'] || !isset($cfg['HTMLtableCells.'][$cc . '.']['callRecursive']) && $cfg['HTMLtableCells.']['default.']['callRecursive']) {
4395  if ($cfg['HTMLtableCells.']['addChr10BetweenParagraphs']) {
4396  $colParts[$kkk] = str_replace('</p><p>', '</p>' . LF . '<p>', $colParts[$kkk]);
4397  }
4398  $colParts[$kkk] = $this->parseFunc($colParts[$kkk], $conf);
4399  }
4400  $tagStdWrap = is_array($cfg['HTMLtableCells.'][$cc . '.']['tagStdWrap.'])
4401  ? $cfg['HTMLtableCells.'][$cc . '.']['tagStdWrap.']
4402  : $cfg['HTMLtableCells.']['default.']['tagStdWrap.'];
4403  if (is_array($tagStdWrap)) {
4404  $tag = $this->stdWrap($tag, $tagStdWrap);
4405  }
4406  $stdWrap = is_array($cfg['HTMLtableCells.'][$cc . '.']['stdWrap.'])
4407  ? $cfg['HTMLtableCells.'][$cc . '.']['stdWrap.']
4408  : $cfg['HTMLtableCells.']['default.']['stdWrap.'];
4409  if (is_array($stdWrap)) {
4410  $colParts[$kkk] = $this->stdWrap($colParts[$kkk], $stdWrap);
4411  }
4412  $colParts[$kkk] = $tag . $colParts[$kkk] . '</' . $tagName . '>';
4413  }
4414  }
4415  $rowParts[$kk] = implode('', $colParts);
4416  }
4417  }
4418  $parts[$k] = implode('', $rowParts);
4419  }
4420  if (is_array($cfg['stdWrap.'])) {
4421  $parts[$k] = $this->stdWrap($parts[$k], $cfg['stdWrap.']);
4422  }
4423  } else {
4424  $parts[$k] = $this->_parseFunc($parts[$k], $conf);
4425  }
4426  }
4427  return implode('', $parts);
4428  }
4429 
4439  public function _parseFunc($theValue, $conf)
4440  {
4441  if (!empty($conf['if.']) && !$this->checkIf($conf['if.'])) {
4442  return $theValue;
4443  }
4444  // Indicates that the data is from within a tag.
4445  $inside = false;
4446  // Pointer to the total string position
4447  $pointer = 0;
4448  // Loaded with the current typo-tag if any.
4449  $currentTag = '';
4450  $stripNL = 0;
4451  $contentAccum = [];
4452  $contentAccumP = 0;
4453  $allowTags = strtolower(str_replace(' ', '', $conf['allowTags']));
4454  $denyTags = strtolower(str_replace(' ', '', $conf['denyTags']));
4455  $totalLen = strlen($theValue);
4456  do {
4457  if (!$inside) {
4458  if (!is_array($currentTag)) {
4459  // These operations should only be performed on code outside the typotags...
4460  // data: this checks that we enter tags ONLY if the first char in the tag is alphanumeric OR '/'
4461  $len_p = 0;
4462  $c = 100;
4463  do {
4464  $len = strcspn(substr($theValue, $pointer + $len_p), '<');
4465  $len_p += $len + 1;
4466  $endChar = ord(strtolower(substr($theValue, $pointer + $len_p, 1)));
4467  $c--;
4468  } while ($c > 0 && $endChar && ($endChar < 97 || $endChar > 122) && $endChar != 47);
4469  $len = $len_p - 1;
4470  } else {
4471  // If we're inside a currentTag, just take it to the end of that tag!
4472  $tempContent = strtolower(substr($theValue, $pointer));
4473  $len = strpos($tempContent, '</' . $currentTag[0]);
4474  if (is_string($len) && !$len) {
4475  $len = strlen($tempContent);
4476  }
4477  }
4478  // $data is the content until the next <tag-start or end is detected.
4479  // In case of a currentTag set, this would mean all data between the start- and end-tags
4480  $data = substr($theValue, $pointer, $len);
4481  if ($data != '') {
4482  if ($stripNL) {
4483  // If the previous tag was set to strip NewLines in the beginning of the next data-chunk.
4484  $data = preg_replace('/^[ ]*' . CR . '?' . LF . '/', '', $data);
4485  }
4486  // These operations should only be performed on code outside the tags...
4487  if (!is_array($currentTag)) {
4488  // Constants
4489  $tsfe = $this->getTypoScriptFrontendController();
4490  $tmpConstants = $tsfe->tmpl->setup['constants.'];
4491  if ($conf['constants'] && is_array($tmpConstants)) {
4492  foreach ($tmpConstants as $key => $val) {
4493  if (is_string($val)) {
4494  $data = str_replace('###' . $key . '###', $val, $data);
4495  }
4496  }
4497  }
4498  // Short
4499  if (is_array($conf['short.'])) {
4500  $shortWords = $conf['short.'];
4501  krsort($shortWords);
4502  foreach ($shortWords as $key => $val) {
4503  if (is_string($val)) {
4504  $data = str_replace($key, $val, $data);
4505  }
4506  }
4507  }
4508  // stdWrap
4509  if (is_array($conf['plainTextStdWrap.'])) {
4510  $data = $this->stdWrap($data, $conf['plainTextStdWrap.']);
4511  }
4512  // userFunc
4513  if ($conf['userFunc']) {
4514  $data = $this->callUserFunction($conf['userFunc'], $conf['userFunc.'], $data);
4515  }
4516  // Makelinks: (Before search-words as we need the links to be generated when searchwords go on...!)
4517  if ($conf['makelinks']) {
4518  $data = $this->http_makelinks($data, $conf['makelinks.']['http.']);
4519  $data = $this->mailto_makelinks($data, $conf['makelinks.']['mailto.']);
4520  }
4521  // Search Words:
4522  if ($tsfe->no_cache && $conf['sword'] && is_array($tsfe->sWordList) && $tsfe->sWordRegEx) {
4523  $newstring = '';
4524  do {
4525  $pregSplitMode = 'i';
4526  if (isset($tsfe->config['config']['sword_noMixedCase']) && !empty($tsfe->config['config']['sword_noMixedCase'])) {
4527  $pregSplitMode = '';
4528  }
4529  $pieces = preg_split('/' . $tsfe->sWordRegEx . '/' . $pregSplitMode, $data, 2);
4530  $newstring .= $pieces[0];
4531  $match_len = strlen($data) - (strlen($pieces[0]) + strlen($pieces[1]));
4532  $inTag = false;
4533  if (strstr($pieces[0], '<') || strstr($pieces[0], '>')) {
4534  // Returns TRUE, if a '<' is closer to the string-end than '>'.
4535  // This is the case if we're INSIDE a tag (that could have been
4536  // made by makelinks...) and we must secure, that the inside of a tag is
4537  // not marked up.
4538  $inTag = strrpos($pieces[0], '<') > strrpos($pieces[0], '>');
4539  }
4540  // The searchword:
4541  $match = substr($data, strlen($pieces[0]), $match_len);
4542  if (trim($match) && strlen($match) > 1 && !$inTag) {
4543  $match = $this->wrap($match, $conf['sword']);
4544  }
4545  // Concatenate the Search Word again.
4546  $newstring .= $match;
4547  $data = $pieces[1];
4548  } while ($pieces[1]);
4549  $data = $newstring;
4550  }
4551  }
4552  $contentAccum[$contentAccumP] .= $data;
4553  }
4554  $inside = true;
4555  } else {
4556  // tags
4557  $len = strcspn(substr($theValue, $pointer), '>') + 1;
4558  $data = substr($theValue, $pointer, $len);
4559  if (StringUtility::endsWith($data, '/>') && strpos($data, '<link ') !== 0) {
4560  $tagContent = substr($data, 1, -2);
4561  } else {
4562  $tagContent = substr($data, 1, -1);
4563  }
4564  $tag = explode(' ', trim($tagContent), 2);
4565  $tag[0] = strtolower($tag[0]);
4566  if ($tag[0][0] === '/') {
4567  $tag[0] = substr($tag[0], 1);
4568  $tag['out'] = 1;
4569  }
4570  if ($conf['tags.'][$tag[0]]) {
4571  $treated = false;
4572  $stripNL = false;
4573  // in-tag
4574  if (!$currentTag && !$tag['out']) {
4575  // $currentTag (array!) is the tag we are currently processing
4576  $currentTag = $tag;
4577  $contentAccumP++;
4578  $treated = true;
4579  // in-out-tag: img and other empty tags
4580  if (preg_match('/^(area|base|br|col|hr|img|input|meta|param)$/i', $tag[0])) {
4581  $tag['out'] = 1;
4582  }
4583  }
4584  // out-tag
4585  if ($currentTag[0] === $tag[0] && $tag['out']) {
4586  $theName = $conf['tags.'][$tag[0]];
4587  $theConf = $conf['tags.'][$tag[0] . '.'];
4588  // This flag indicates, that NL- (13-10-chars) should be stripped first and last.
4589  $stripNL = (bool)$theConf['stripNL'];
4590  // This flag indicates, that this TypoTag section should NOT be included in the nonTypoTag content.
4591  $breakOut = (bool)$theConf['breakoutTypoTagContent'];
4592  $this->parameters = [];
4593  if ($currentTag[1]) {
4594  $params = GeneralUtility::get_tag_attributes($currentTag[1]);
4595  if (is_array($params)) {
4596  foreach ($params as $option => $val) {
4597  $this->parameters[strtolower($option)] = $val;
4598  }
4599  }
4600  }
4601  $this->parameters['allParams'] = trim($currentTag[1]);
4602  // Removes NL in the beginning and end of the tag-content AND at the end of the currentTagBuffer.
4603  // $stripNL depends on the configuration of the current tag
4604  if ($stripNL) {
4605  $contentAccum[$contentAccumP - 1] = preg_replace('/' . CR . '?' . LF . '[ ]*$/', '', $contentAccum[$contentAccumP - 1]);
4606  $contentAccum[$contentAccumP] = preg_replace('/^[ ]*' . CR . '?' . LF . '/', '', $contentAccum[$contentAccumP]);
4607  $contentAccum[$contentAccumP] = preg_replace('/' . CR . '?' . LF . '[ ]*$/', '', $contentAccum[$contentAccumP]);
4608  }
4609  $this->data[$this->currentValKey] = $contentAccum[$contentAccumP];
4610  $newInput = $this->cObjGetSingle($theName, $theConf, '/parseFunc/.tags.' . $tag[0]);
4611  // fetch the content object
4612  $contentAccum[$contentAccumP] = $newInput;
4613  $contentAccumP++;
4614  // If the TypoTag section
4615  if (!$breakOut) {
4616  $contentAccum[$contentAccumP - 2] .= $contentAccum[$contentAccumP - 1] . $contentAccum[$contentAccumP];
4617  unset($contentAccum[$contentAccumP]);
4618  unset($contentAccum[$contentAccumP - 1]);
4619  $contentAccumP -= 2;
4620  }
4621  unset($currentTag);
4622  $treated = true;
4623  }
4624  // other tags
4625  if (!$treated) {
4626  $contentAccum[$contentAccumP] .= $data;
4627  }
4628  } else {
4629  // If a tag was not a typo tag, then it is just added to the content
4630  $stripNL = false;
4631  if (GeneralUtility::inList($allowTags, $tag[0]) || $denyTags !== '*' && !GeneralUtility::inList($denyTags, $tag[0])) {
4632  $contentAccum[$contentAccumP] .= $data;
4633  } else {
4634  $contentAccum[$contentAccumP] .= htmlspecialchars($data);
4635  }
4636  }
4637  $inside = false;
4638  }
4639  $pointer += $len;
4640  } while ($pointer < $totalLen);
4641  // Parsing nonTypoTag content (all even keys):
4642  reset($contentAccum);
4643  $contentAccumCount = count($contentAccum);
4644  for ($a = 0; $a < $contentAccumCount; $a++) {
4645  if ($a % 2 != 1) {
4646  // stdWrap
4647  if (is_array($conf['nonTypoTagStdWrap.'])) {
4648  $contentAccum[$a] = $this->stdWrap($contentAccum[$a], $conf['nonTypoTagStdWrap.']);
4649  }
4650  // userFunc
4651  if ($conf['nonTypoTagUserFunc']) {
4652  $contentAccum[$a] = $this->callUserFunction($conf['nonTypoTagUserFunc'], $conf['nonTypoTagUserFunc.'], $contentAccum[$a]);
4653  }
4654  }
4655  }
4656  return implode('', $contentAccum);
4657  }
4658 
4667  public function encaps_lineSplit($theValue, $conf)
4668  {
4669  if ((string)$theValue === '') {
4670  return '';
4671  }
4672  $lParts = explode(LF, $theValue);
4673 
4674  // When the last element is an empty linebreak we need to remove it, otherwise we will have a duplicate empty line.
4675  $lastPartIndex = count($lParts) - 1;
4676  if ($lParts[$lastPartIndex] === '' && trim($lParts[$lastPartIndex - 1], CR) === '') {
4677  array_pop($lParts);
4678  }
4679 
4680  $encapTags = GeneralUtility::trimExplode(',', strtolower($conf['encapsTagList']), true);
4681  $nonWrappedTag = $conf['nonWrappedTag'];
4682  $defaultAlign = isset($conf['defaultAlign.'])
4683  ? trim($this->stdWrap($conf['defaultAlign'], $conf['defaultAlign.']))
4684  : trim($conf['defaultAlign']);
4685 
4686  $str_content = '';
4687  foreach ($lParts as $k => $l) {
4688  $sameBeginEnd = 0;
4689  $emptyTag = false;
4690  $l = trim($l);
4691  $attrib = [];
4692  $nonWrapped = false;
4693  $tagName = '';
4694  if ($l[0] === '<' && substr($l, -1) === '>') {
4695  $fwParts = explode('>', substr($l, 1), 2);
4696  list($tagName) = explode(' ', $fwParts[0], 2);
4697  if (!$fwParts[1]) {
4698  if (substr($tagName, -1) === '/') {
4699  $tagName = substr($tagName, 0, -1);
4700  }
4701  if (substr($fwParts[0], -1) === '/') {
4702  $sameBeginEnd = 1;
4703  $emptyTag = true;
4704  $attrib = GeneralUtility::get_tag_attributes('<' . substr($fwParts[0], 0, -1) . '>');
4705  }
4706  } else {
4707  $backParts = GeneralUtility::revExplode('<', substr($fwParts[1], 0, -1), 2);
4708  $attrib = GeneralUtility::get_tag_attributes('<' . $fwParts[0] . '>');
4709  $str_content = $backParts[0];
4710  $sameBeginEnd = substr(strtolower($backParts[1]), 1, strlen($tagName)) === strtolower($tagName);
4711  }
4712  }
4713  if ($sameBeginEnd && in_array(strtolower($tagName), $encapTags)) {
4714  $uTagName = strtoupper($tagName);
4715  $uTagName = strtoupper($conf['remapTag.'][$uTagName] ? $conf['remapTag.'][$uTagName] : $uTagName);
4716  } else {
4717  $uTagName = strtoupper($nonWrappedTag);
4718  // The line will be wrapped: $uTagName should not be an empty tag
4719  $emptyTag = false;
4720  $str_content = $lParts[$k];
4721  $nonWrapped = true;
4722  $attrib = [];
4723  }
4724  // Wrapping all inner-content:
4725  if (is_array($conf['innerStdWrap_all.'])) {
4726  $str_content = $this->stdWrap($str_content, $conf['innerStdWrap_all.']);
4727  }
4728  if ($uTagName) {
4729  // Setting common attributes
4730  if (is_array($conf['addAttributes.'][$uTagName . '.'])) {
4731  foreach ($conf['addAttributes.'][$uTagName . '.'] as $kk => $vv) {
4732  if (!is_array($vv)) {
4733  if ((string)$conf['addAttributes.'][$uTagName . '.'][$kk . '.']['setOnly'] === 'blank') {
4734  if ((string)$attrib[$kk] === '') {
4735  $attrib[$kk] = $vv;
4736  }
4737  } elseif ((string)$conf['addAttributes.'][$uTagName . '.'][$kk . '.']['setOnly'] === 'exists') {
4738  if (!isset($attrib[$kk])) {
4739  $attrib[$kk] = $vv;
4740  }
4741  } else {
4742  $attrib[$kk] = $vv;
4743  }
4744  }
4745  }
4746  }
4747  // Wrapping all inner-content:
4748  if (is_array($conf['encapsLinesStdWrap.'][$uTagName . '.'])) {
4749  $str_content = $this->stdWrap($str_content, $conf['encapsLinesStdWrap.'][$uTagName . '.']);
4750  }
4751  // Default align
4752  if (!$attrib['align'] && $defaultAlign) {
4753  $attrib['align'] = $defaultAlign;
4754  }
4755  $params = GeneralUtility::implodeAttributes($attrib, true);
4756  if (!$conf['removeWrapping'] || ($emptyTag && $conf['removeWrapping.']['keepSingleTag'])) {
4757  $selfClosingTagList = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
4758  if ($emptyTag && in_array(strtolower($uTagName), $selfClosingTagList, true)) {
4759  $str_content = '<' . strtolower($uTagName) . (trim($params) ? ' ' . trim($params) : '') . ' />';
4760  } else {
4761  $str_content = '<' . strtolower($uTagName) . (trim($params) ? ' ' . trim($params) : '') . '>' . $str_content . '</' . strtolower($uTagName) . '>';
4762  }
4763  }
4764  }
4765  if ($nonWrapped && $conf['wrapNonWrappedLines']) {
4766  $str_content = $this->wrap($str_content, $conf['wrapNonWrappedLines']);
4767  }
4768  $lParts[$k] = $str_content;
4769  }
4770  return implode(LF, $lParts);
4771  }
4772 
4783  public function http_makelinks($data, $conf)
4784  {
4785  $aTagParams = $this->getATagParams($conf);
4786  $textstr = '';
4787  foreach ([ 'http://', 'https://' ] as $scheme) {
4788  $textpieces = explode($scheme, $data);
4789  $pieces = count($textpieces);
4790  $textstr = $textpieces[0];
4791  for ($i = 1; $i < $pieces; $i++) {
4792  $len = strcspn($textpieces[$i], chr(32) . TAB . CRLF);
4793  if (trim(substr($textstr, -1)) === '' && $len) {
4794  $lastChar = substr($textpieces[$i], $len - 1, 1);
4795  if (!preg_match('/[A-Za-z0-9\\/#_-]/', $lastChar)) {
4796  $len--;
4797  }
4798  // Included '\/' 3/12
4799  $parts[0] = substr($textpieces[$i], 0, $len);
4800  $parts[1] = substr($textpieces[$i], $len);
4801  $keep = $conf['keep'];
4802  $linkParts = parse_url($scheme . $parts[0]);
4803  $linktxt = '';
4804  if (strstr($keep, 'scheme')) {
4805  $linktxt = $scheme;
4806  }
4807  $linktxt .= $linkParts['host'];
4808  if (strstr($keep, 'path')) {
4809  $linktxt .= $linkParts['path'];
4810  // Added $linkParts['query'] 3/12
4811  if (strstr($keep, 'query') && $linkParts['query']) {
4812  $linktxt .= '?' . $linkParts['query'];
4813  } elseif ($linkParts['path'] === '/') {
4814  $linktxt = substr($linktxt, 0, -1);
4815  }
4816  }
4817  if (isset($conf['extTarget'])) {
4818  if (isset($conf['extTarget.'])) {
4819  $target = $this->stdWrap($conf['extTarget'], $conf['extTarget.']);
4820  } else {
4821  $target = $conf['extTarget'];
4822  }
4823  } else {
4824  $target = $this->getTypoScriptFrontendController()->extTarget;
4825  }
4826 
4827  // check for jump URLs or similar
4828  $linkUrl = $this->processUrl(UrlProcessorInterface::CONTEXT_COMMON, $scheme . $parts[0], $conf);
4829 
4830  $res = '<a href="' . htmlspecialchars($linkUrl) . '"'
4831  . ($target !== '' ? ' target="' . htmlspecialchars($target) . '"' : '')
4832  . $aTagParams . $this->extLinkATagParams(('http://' . $parts[0]), 'url') . '>';
4833 
4834  $wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
4835  if ((string)$conf['ATagBeforeWrap'] !== '') {
4836  $res = $res . $this->wrap($linktxt, $wrap) . '</a>';
4837  } else {
4838  $res = $this->wrap($res . $linktxt . '</a>', $wrap);
4839  }
4840  $textstr .= $res . $parts[1];
4841  } else {
4842  $textstr .= $scheme . $textpieces[$i];
4843  }
4844  }
4845  $data = $textstr;
4846  }
4847  return $textstr;
4848  }
4849 
4859  public function mailto_makelinks($data, $conf)
4860  {
4861  // http-split
4862  $aTagParams = $this->getATagParams($conf);
4863  $textpieces = explode('mailto:', $data);
4864  $pieces = count($textpieces);
4865  $textstr = $textpieces[0];
4866  $tsfe = $this->getTypoScriptFrontendController();
4867  for ($i = 1; $i < $pieces; $i++) {
4868  $len = strcspn($textpieces[$i], chr(32) . TAB . CRLF);
4869  if (trim(substr($textstr, -1)) === '' && $len) {
4870  $lastChar = substr($textpieces[$i], $len - 1, 1);
4871  if (!preg_match('/[A-Za-z0-9]/', $lastChar)) {
4872  $len--;
4873  }
4874  $parts[0] = substr($textpieces[$i], 0, $len);
4875  $parts[1] = substr($textpieces[$i], $len);
4876  $linktxt = preg_replace('/\\?.*/', '', $parts[0]);
4877  list($mailToUrl, $linktxt) = $this->getMailTo($parts[0], $linktxt);
4878  $mailToUrl = $tsfe->spamProtectEmailAddresses === 'ascii' ? $mailToUrl : htmlspecialchars($mailToUrl);
4879  $res = '<a href="' . $mailToUrl . '"' . $aTagParams . '>';
4880  $wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
4881  if ((string)$conf['ATagBeforeWrap'] !== '') {
4882  $res = $res . $this->wrap($linktxt, $wrap) . '</a>';
4883  } else {
4884  $res = $this->wrap($res . $linktxt . '</a>', $wrap);
4885  }
4886  $textstr .= $res . $parts[1];
4887  } else {
4888  $textstr .= 'mailto:' . $textpieces[$i];
4889  }
4890  }
4891  return $textstr;
4892  }
4893 
4918  public function getImgResource($file, $fileArray)
4919  {
4920  if (empty($file) && empty($fileArray)) {
4921  return null;
4922  }
4923  if (!is_array($fileArray)) {
4924  $fileArray = (array)$fileArray;
4925  }
4926  $imageResource = null;
4927  $tsfe = $this->getTypoScriptFrontendController();
4928  if ($file === 'GIFBUILDER') {
4930  $gifCreator = GeneralUtility::makeInstance(GifBuilder::class);
4931  $gifCreator->init();
4932  $theImage = '';
4933  if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib']) {
4934  $gifCreator->start($fileArray, $this->data);
4935  $theImage = $gifCreator->gifBuild();
4936  }
4937  $imageResource = $gifCreator->getImageDimensions($theImage);
4938  $imageResource['origFile'] = $theImage;
4939  } else {
4940  if ($file instanceof File) {
4941  $fileObject = $file;
4942  } elseif ($file instanceof FileReference) {
4943  $fileObject = $file->getOriginalFile();
4944  } else {
4945  try {
4946  if ($fileArray['import.']) {
4947  $importedFile = trim($this->stdWrap('', $fileArray['import.']));
4948  if (!empty($importedFile)) {
4949  $file = $importedFile;
4950  }
4951  }
4952 
4954  $treatIdAsReference = isset($fileArray['treatIdAsReference.']) ? $this->stdWrap($fileArray['treatIdAsReference'], $fileArray['treatIdAsReference.']) : $fileArray['treatIdAsReference'];
4955  if (!empty($treatIdAsReference)) {
4956  $file = $this->getResourceFactory()->getFileReferenceObject($file);
4957  $fileObject = $file->getOriginalFile();
4958  } else {
4959  $fileObject = $this->getResourceFactory()->getFileObject($file);
4960  }
4961  } elseif (preg_match('/^(0|[1-9][0-9]*):/', $file)) { // combined identifier
4962  $fileObject = $this->getResourceFactory()->retrieveFileOrFolderObject($file);
4963  } else {
4964  if (isset($importedFile) && !empty($importedFile) && !empty($fileArray['import'])) {
4965  $file = $fileArray['import'] . $file;
4966  }
4967  $fileObject = $this->getResourceFactory()->retrieveFileOrFolderObject($file);
4968  }
4969  } catch (Exception $exception) {
4971  $logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
4972  $logger->warning('The image "' . $file . '" could not be found and won\'t be included in frontend output', ['exception' => $exception]);
4973  return null;
4974  }
4975  }
4976  if ($fileObject instanceof File) {
4977  $processingConfiguration = [];
4978  $processingConfiguration['width'] = isset($fileArray['width.']) ? $this->stdWrap($fileArray['width'], $fileArray['width.']) : $fileArray['width'];
4979  $processingConfiguration['height'] = isset($fileArray['height.']) ? $this->stdWrap($fileArray['height'], $fileArray['height.']) : $fileArray['height'];
4980  $processingConfiguration['fileExtension'] = isset($fileArray['ext.']) ? $this->stdWrap($fileArray['ext'], $fileArray['ext.']) : $fileArray['ext'];
4981  $processingConfiguration['maxWidth'] = isset($fileArray['maxW.']) ? (int)$this->stdWrap($fileArray['maxW'], $fileArray['maxW.']) : (int)$fileArray['maxW'];
4982  $processingConfiguration['maxHeight'] = isset($fileArray['maxH.']) ? (int)$this->stdWrap($fileArray['maxH'], $fileArray['maxH.']) : (int)$fileArray['maxH'];
4983  $processingConfiguration['minWidth'] = isset($fileArray['minW.']) ? (int)$this->stdWrap($fileArray['minW'], $fileArray['minW.']) : (int)$fileArray['minW'];
4984  $processingConfiguration['minHeight'] = isset($fileArray['minH.']) ? (int)$this->stdWrap($fileArray['minH'], $fileArray['minH.']) : (int)$fileArray['minH'];
4985  $processingConfiguration['noScale'] = isset($fileArray['noScale.']) ? $this->stdWrap($fileArray['noScale'], $fileArray['noScale.']) : $fileArray['noScale'];
4986  $processingConfiguration['additionalParameters'] = isset($fileArray['params.']) ? $this->stdWrap($fileArray['params'], $fileArray['params.']) : $fileArray['params'];
4987  $processingConfiguration['frame'] = isset($fileArray['frame.']) ? (int)$this->stdWrap($fileArray['frame'], $fileArray['frame.']) : (int)$fileArray['frame'];
4988  if ($file instanceof FileReference) {
4989  $processingConfiguration['crop'] = $this->getCropAreaFromFileReference($file, $fileArray);
4990  } else {
4991  $processingConfiguration['crop'] = $this->getCropAreaFromFromTypoScriptSettings($fileObject, $fileArray);
4992  }
4993 
4994  // Possibility to cancel/force profile extraction
4995  // see $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand']
4996  if (isset($fileArray['stripProfile'])) {
4997  $processingConfiguration['stripProfile'] = $fileArray['stripProfile'];
4998  }
4999  // Check if we can handle this type of file for editing
5000  if (GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], $fileObject->getExtension())) {
5001  $maskArray = $fileArray['m.'];
5002  // Must render mask images and include in hash-calculating
5003  // - otherwise we cannot be sure the filename is unique for the setup!
5004  if (is_array($maskArray)) {
5005  $mask = $this->getImgResource($maskArray['mask'], $maskArray['mask.']);
5006  $bgImg = $this->getImgResource($maskArray['bgImg'], $maskArray['bgImg.']);
5007  $bottomImg = $this->getImgResource($maskArray['bottomImg'], $maskArray['bottomImg.']);
5008  $bottomImg_mask = $this->getImgResource($maskArray['bottomImg_mask'], $maskArray['bottomImg_mask.']);
5009 
5010  $processingConfiguration['maskImages']['maskImage'] = $mask['processedFile'];
5011  $processingConfiguration['maskImages']['backgroundImage'] = $bgImg['processedFile'];
5012  $processingConfiguration['maskImages']['maskBottomImage'] = $bottomImg['processedFile'];
5013  $processingConfiguration['maskImages']['maskBottomImageMask'] = $bottomImg_mask['processedFile'];
5014  }
5015  $processedFileObject = $fileObject->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $processingConfiguration);
5016  $hash = $processedFileObject->calculateChecksum();
5017  // store info in the TSFE template cache (kept for backwards compatibility)
5018  if ($processedFileObject->isProcessed() && !isset($tsfe->tmpl->fileCache[$hash])) {
5019  $tsfe->tmpl->fileCache[$hash] = [
5020  0 => (int)$processedFileObject->getProperty('width'),
5021  1 => (int)$processedFileObject->getProperty('height'),
5022  2 => $processedFileObject->getExtension(),
5023  3 => $processedFileObject->getPublicUrl(),
5024  'origFile' => $fileObject->getPublicUrl(),
5025  'origFile_mtime' => $fileObject->getModificationTime(),
5026  // This is needed by \TYPO3\CMS\Frontend\Imaging\GifBuilder,
5027  // in order for the setup-array to create a unique filename hash.
5028  'originalFile' => $fileObject,
5029  'processedFile' => $processedFileObject,
5030  'fileCacheHash' => $hash
5031  ];
5032  }
5033  $imageResource = $tsfe->tmpl->fileCache[$hash];
5034  }
5035  }
5036  }
5037  // If image was processed by GIFBUILDER:
5038  // ($imageResource indicates that it was processed the regular way)
5039  if (!isset($imageResource)) {
5040  $theImage = $tsfe->tmpl->getFileName($file);
5041  if ($theImage) {
5042  $gifCreator = GeneralUtility::makeInstance(GifBuilder::class);
5044  $gifCreator->init();
5045  $info = $gifCreator->imageMagickConvert($theImage, 'WEB');
5046  $info['origFile'] = $theImage;
5047  // This is needed by \TYPO3\CMS\Frontend\Imaging\GifBuilder, ln 100ff in order for the setup-array to create a unique filename hash.
5048  $info['origFile_mtime'] = @filemtime($theImage);
5049  $imageResource = $info;
5050  }
5051  }
5052  // Hook 'getImgResource': Post-processing of image resources
5053  if (isset($imageResource)) {
5055  foreach ($this->getGetImgResourceHookObjects() as $hookObject) {
5056  $imageResource = $hookObject->getImgResourcePostProcess($file, (array)$fileArray, $imageResource, $this);
5057  }
5058  }
5059  return $imageResource;
5060  }
5061 
5079  protected function getCropAreaFromFileReference(FileReference $fileReference, array $fileArray)
5080  {
5082  $cropArea = null;
5083  // Use cropping area from file reference if nothing is configured in TypoScript.
5084  if (!isset($fileArray['crop']) && !isset($fileArray['crop.'])) {
5085  // Set crop variant from TypoScript settings. If not set, use default.
5086  $cropVariant = $fileArray['cropVariant'] ?? 'default';
5087  $fileCropArea = $this->createCropAreaFromJsonString((string)$fileReference->getProperty('crop'), $cropVariant);
5088  return $fileCropArea->isEmpty() ? null : $fileCropArea->makeAbsoluteBasedOnFile($fileReference);
5089  }
5090 
5091  return $this->getCropAreaFromFromTypoScriptSettings($fileReference, $fileArray);
5092  }
5093 
5102  protected function getCropAreaFromFromTypoScriptSettings(FileInterface $file, array $fileArray)
5103  {
5105  $cropArea = null;
5106  // Resolve TypoScript configured cropping.
5107  $cropSettings = isset($fileArray['crop.'])
5108  ? $this->stdWrap($fileArray['crop'], $fileArray['crop.'])
5109  : ($fileArray['crop'] ?? null);
5110 
5111  if (is_string($cropSettings)) {
5112  // Set crop variant from TypoScript settings. If not set, use default.
5113  $cropVariant = $fileArray['cropVariant'] ?? 'default';
5114  // Get cropArea from CropVariantCollection, if cropSettings is a valid json.
5115  // CropVariantCollection::create does json_decode.
5116  $jsonCropArea = $this->createCropAreaFromJsonString($cropSettings, $cropVariant);
5117  $cropArea = $jsonCropArea->isEmpty() ? null : $jsonCropArea->makeAbsoluteBasedOnFile($file);
5118 
5119  // Cropping is configured in TypoScript in the following way: file.crop = 50,50,100,100
5120  if ($jsonCropArea->isEmpty() && preg_match('/^[0-9]+,[0-9]+,[0-9]+,[0-9]+$/', $cropSettings)) {
5121  $cropSettings = explode(',', $cropSettings);
5122  if (count($cropSettings) === 4) {
5123  $stringCropArea = GeneralUtility::makeInstance(
5124  Area::class,
5125  ...$cropSettings
5126  );
5127  $cropArea = $stringCropArea->isEmpty() ? null : $stringCropArea;
5128  }
5129  }
5130  }
5131 
5132  return $cropArea;
5133  }
5134 
5143  protected function createCropAreaFromJsonString(string $cropSettings, string $cropVariant): Area
5144  {
5145  return CropVariantCollection::create($cropSettings)->getCropArea($cropVariant);
5146  }
5147 
5148  /***********************************************
5149  *
5150  * Data retrieval etc.
5151  *
5152  ***********************************************/
5159  public function getFieldVal($field)
5160  {
5161  if (!strstr($field, '//')) {
5162  return $this->data[trim($field)];
5163  }
5164  $sections = GeneralUtility::trimExplode('//', $field, true);
5165  foreach ($sections as $k) {
5166  if ((string)$this->data[$k] !== '') {
5167  return $this->data[$k];
5168  }
5169  }
5170 
5171  return '';
5172  }
5173 
5182  public function getData($string, $fieldArray = null)
5183  {
5184  $tsfe = $this->getTypoScriptFrontendController();
5185  if (!is_array($fieldArray)) {
5186  $fieldArray = $tsfe->page;
5187  }
5188  $retVal = '';
5189  $sections = explode('//', $string);
5190  foreach ($sections as $secKey => $secVal) {
5191  if ($retVal) {
5192  break;
5193  }
5194  $parts = explode(':', $secVal, 2);
5195  $type = strtolower(trim($parts[0]));
5196  $typesWithOutParameters = ['level', 'date', 'current', 'pagelayout'];
5197  $key = trim($parts[1]);
5198  if (($key != '') || in_array($type, $typesWithOutParameters)) {
5199  switch ($type) {
5200  case 'gp':
5201  // Merge GET and POST and get $key out of the merged array
5202  $getPostArray = GeneralUtility::_GET();
5204  $retVal = $this->getGlobal($key, $getPostArray);
5205  break;
5206  case 'tsfe':
5207  $retVal = $this->getGlobal('TSFE|' . $key);
5208  break;
5209  case 'getenv':
5210  $retVal = getenv($key);
5211  break;
5212  case 'getindpenv':
5213  $retVal = $this->getEnvironmentVariable($key);
5214  break;
5215  case 'field':
5216  $retVal = $this->getGlobal($key, $fieldArray);
5217  break;
5218  case 'file':
5219  $retVal = $this->getFileDataKey($key);
5220  break;
5221  case 'parameters':
5222  $retVal = $this->parameters[$key];
5223  break;
5224  case 'register':
5225  $retVal = $tsfe->register[$key];
5226  break;
5227  case 'global':
5228  $retVal = $this->getGlobal($key);
5229  break;
5230  case 'level':
5231  $retVal = count($tsfe->tmpl->rootLine) - 1;
5232  break;
5233  case 'leveltitle':
5234  $keyParts = GeneralUtility::trimExplode(',', $key);
5235  $numericKey = $this->getKey($keyParts[0], $tsfe->tmpl->rootLine);
5236  $retVal = $this->rootLineValue($numericKey, 'title', strtolower($keyParts[1]) === 'slide');
5237  break;
5238  case 'levelmedia':
5239  $keyParts = GeneralUtility::trimExplode(',', $key);
5240  $numericKey = $this->getKey($keyParts[0], $tsfe->tmpl->rootLine);
5241  $retVal = $this->rootLineValue($numericKey, 'media', strtolower($keyParts[1]) === 'slide');
5242  break;
5243  case 'leveluid':
5244  $numericKey = $this->getKey($key, $tsfe->tmpl->rootLine);
5245  $retVal = $this->rootLineValue($numericKey, 'uid');
5246  break;
5247  case 'levelfield':
5248  $keyParts = GeneralUtility::trimExplode(',', $key);
5249  $numericKey = $this->getKey($keyParts[0], $tsfe->tmpl->rootLine);
5250  $retVal = $this->rootLineValue($numericKey, $keyParts[1], strtolower($keyParts[2]) === 'slide');
5251  break;
5252  case 'fullrootline':
5253  $keyParts = GeneralUtility::trimExplode(',', $key);
5254  $fullKey = (int)$keyParts[0] - count($tsfe->tmpl->rootLine) + count($tsfe->rootLine);
5255  if ($fullKey >= 0) {
5256  $retVal = $this->rootLineValue($fullKey, $keyParts[1], stristr($keyParts[2], 'slide'), $tsfe->rootLine);
5257  }
5258  break;
5259  case 'date':
5260  if (!$key) {
5261  $key = 'd/m Y';
5262  }
5263  $retVal = date($key, $GLOBALS['EXEC_TIME']);
5264  break;
5265  case 'page':
5266  $retVal = $tsfe->page[$key];
5267  break;
5268  case 'pagelayout':
5269  // Check if the current page has a value in the DB field "backend_layout"
5270  // if empty, check the root line for "backend_layout_next_level"
5271  // same as
5272  // field = backend_layout
5273  // ifEmpty.data = levelfield:-2, backend_layout_next_level, slide
5274  // ifEmpty.ifEmpty = default
5275  $retVal = $GLOBALS['TSFE']->page['backend_layout'];
5276 
5277  // If it is set to "none" - don't use any
5278  if ($retVal === '-1') {
5279  $retVal = 'none';
5280  } elseif ($retVal === '' || $retVal === '0') {
5281  // If it not set check the root-line for a layout on next level and use this
5282  // Remove first element, which is the current page
5283  // See also \TYPO3\CMS\Backend\View\BackendLayoutView::getSelectedCombinedIdentifier()
5284  $rootLine = $tsfe->rootLine;
5285  array_shift($rootLine);
5286  foreach ($rootLine as $rootLinePage) {
5287  $retVal = (string)$rootLinePage['backend_layout_next_level'];
5288  // If layout for "next level" is set to "none" - don't use any and stop searching
5289  if ($retVal === '-1') {
5290  $retVal = 'none';
5291  break;
5292  }
5293  if ($retVal !== '' && $retVal !== '0') {
5294  // Stop searching if a layout for "next level" is set
5295  break;
5296  }
5297  }
5298  }
5299  if ($retVal === '0' || $retVal === '') {
5300  $retVal = 'default';
5301  }
5302  break;
5303  case 'current':
5304  $retVal = $this->data[$this->currentValKey];
5305  break;
5306  case 'db':
5307  $selectParts = GeneralUtility::trimExplode(':', $key);
5308  $db_rec = $tsfe->sys_page->getRawRecord($selectParts[0], $selectParts[1]);
5309  if (is_array($db_rec) && $selectParts[2]) {
5310  $retVal = $db_rec[$selectParts[2]];
5311  }
5312  break;
5313  case 'lll':
5314  $retVal = $tsfe->sL('LLL:' . $key);
5315  break;
5316  case 'path':
5317  $retVal = $tsfe->tmpl->getFileName($key);
5318  break;
5319  case 'cobj':
5320  switch ($key) {
5321  case 'parentRecordNumber':
5322  $retVal = $this->parentRecordNumber;
5323  break;
5324  }
5325  break;
5326  case 'debug':
5327  switch ($key) {
5328  case 'rootLine':
5329  $retVal = DebugUtility::viewArray($tsfe->tmpl->rootLine);
5330  break;
5331  case 'fullRootLine':
5332  $retVal = DebugUtility::viewArray($tsfe->rootLine);
5333  break;
5334  case 'data':
5335  $retVal = DebugUtility::viewArray($this->data);
5336  break;
5337  case 'register':
5338  $retVal = DebugUtility::viewArray($tsfe->register);
5339  break;
5340  case 'page':
5341  $retVal = DebugUtility::viewArray($tsfe->page);
5342  break;
5343  }
5344  break;
5345  case 'flexform':
5346  $keyParts = GeneralUtility::trimExplode(':', $key, true);
5347  if (count($keyParts) === 2 && isset($this->data[$keyParts[0]])) {
5348  $flexFormContent = $this->data[$keyParts[0]];
5349  if (!empty($flexFormContent)) {
5350  $flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
5351  $flexFormKey = str_replace('.', '|', $keyParts[1]);
5352  $settings = $flexFormService->convertFlexFormContentToArray($flexFormContent);
5353  $retVal = $this->getGlobal($flexFormKey, $settings);
5354  }
5355  }
5356  break;
5357  case 'session':
5358  $keyParts = GeneralUtility::trimExplode('|', $key, true);
5359  $sessionKey = array_shift($keyParts);
5360  $retVal = $this->getTypoScriptFrontendController()->fe_user->getSessionData($sessionKey);
5361  foreach ($keyParts as $keyPart) {
5362  if (is_object($retVal)) {
5363  $retVal = $retVal->{$keyPart};
5364  } elseif (is_array($retVal)) {
5365  $retVal = $retVal[$keyPart];
5366  } else {
5367  $retVal = '';
5368  break;
5369  }
5370  }
5371  if (!is_scalar($retVal)) {
5372  $retVal = '';
5373  }
5374  break;
5375  }
5376  }
5377  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getData'])) {
5378  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getData'] as $classData) {
5379  $hookObject = GeneralUtility::getUserObj($classData);
5380  if (!$hookObject instanceof ContentObjectGetDataHookInterface) {
5381  throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetDataHookInterface::class, 1195044480);
5382  }
5383  $retVal = $hookObject->getDataExtension($string, $fieldArray, $secVal, $retVal, $this);
5384  }
5385  }
5386  }
5387  return $retVal;
5388  }
5389 
5399  protected function getFileDataKey($key)
5400  {
5401  list($fileUidOrCurrentKeyword, $requestedFileInformationKey) = explode(':', $key, 3);
5402  try {
5403  if ($fileUidOrCurrentKeyword === 'current') {
5404  $fileObject = $this->getCurrentFile();
5405  } elseif (MathUtility::canBeInterpretedAsInteger($fileUidOrCurrentKeyword)) {
5407  $fileFactory = GeneralUtility::makeInstance(ResourceFactory::class);
5408  $fileObject = $fileFactory->getFileObject($fileUidOrCurrentKeyword);
5409  } else {
5410  $fileObject = null;
5411  }
5412  } catch (Exception $exception) {
5414  $logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
5415  $logger->warning('The file "' . $fileUidOrCurrentKeyword . '" could not be found and won\'t be included in frontend output', ['exception' => $exception]);
5416  $fileObject = null;
5417  }
5418 
5419  if ($fileObject instanceof FileInterface) {
5420  // All properties of the \TYPO3\CMS\Core\Resource\FileInterface are available here:
5421  switch ($requestedFileInformationKey) {
5422  case 'name':
5423  return $fileObject->getName();
5424  case 'uid':
5425  if (method_exists($fileObject, 'getUid')) {
5426  return $fileObject->getUid();
5427  }
5428  return 0;
5429  case 'originalUid':
5430  if ($fileObject instanceof FileReference) {
5431  return $fileObject->getOriginalFile()->getUid();
5432  }
5433  return null;
5434  case 'size':
5435  return $fileObject->getSize();
5436  case 'sha1':
5437  return $fileObject->getSha1();
5438  case 'extension':
5439  return $fileObject->getExtension();
5440  case 'mimetype':
5441  return $fileObject->getMimeType();
5442  case 'contents':
5443  return $fileObject->getContents();
5444  case 'publicUrl':
5445  return $fileObject->getPublicUrl();
5446  default:
5447  // Generic alternative here
5448  return $fileObject->getProperty($requestedFileInformationKey);
5449  }
5450  } else {
5451  // @todo fail silently as is common in tslib_content
5452  return 'Error: no file object';
5453  }
5454  }
5455 
5467  public function rootLineValue($key, $field, $slideBack = false, $altRootLine = '')
5468  {
5469  $rootLine = is_array($altRootLine) ? $altRootLine : $this->getTypoScriptFrontendController()->tmpl->rootLine;
5470  if (!$slideBack) {
5471  return $rootLine[$key][$field];
5472  }
5473  for ($a = $key; $a >= 0; $a--) {
5474  $val = $rootLine[$a][$field];
5475  if ($val) {
5476  return $val;
5477  }
5478  }
5479 
5480  return '';
5481  }
5482 
5492  public function getGlobal($keyString, $source = null)
5493  {
5494  $keys = explode('|', $keyString);
5495  $numberOfLevels = count($keys);
5496  $rootKey = trim($keys[0]);
5497  $value = isset($source) ? $source[$rootKey] : $GLOBALS[$rootKey];
5498  for ($i = 1; $i < $numberOfLevels && isset($value); $i++) {
5499  $currentKey = trim($keys[$i]);
5500  if (is_object($value)) {
5501  $value = $value->{$currentKey};
5502  } elseif (is_array($value)) {
5503  $value = $value[$currentKey];
5504  } else {
5505  $value = '';
5506  break;
5507  }
5508  }
5509  if (!is_scalar($value)) {
5510  $value = '';
5511  }
5512  return $value;
5513  }
5514 
5525  public function getKey($key, $arr)
5526  {
5527  $key = (int)$key;
5528  if (is_array($arr)) {
5529  if ($key < 0) {
5530  $key = count($arr) + $key;
5531  }
5532  if ($key < 0) {
5533  $key = 0;
5534  }
5535  }
5536  return $key;
5537  }
5538 
5548  public function TCAlookup($inputValue, $conf)
5549  {
5550  $table = $conf['table'];
5551  $field = $conf['field'];
5552  $delimiter = $conf['delimiter'] ? $conf['delimiter'] : ' ,';
5553  if (is_array($GLOBALS['TCA'][$table]) && is_array($GLOBALS['TCA'][$table]['columns'][$field]) && is_array($GLOBALS['TCA'][$table]['columns'][$field]['config']['items'])) {
5554  $tsfe = $this->getTypoScriptFrontendController();
5555  $values = GeneralUtility::trimExplode(',', $inputValue);
5556  $output = [];
5557  foreach ($values as $value) {
5558  // Traverse the items-array...
5559  foreach ($GLOBALS['TCA'][$table]['columns'][$field]['config']['items'] as $item) {
5560  // ... and return the first found label where the value was equal to $key
5561  if ((string)$item[1] === trim($value)) {
5562  $output[] = $tsfe->sL($item[0]);
5563  }
5564  }
5565  }
5566  $returnValue = implode($delimiter, $output);
5567  } else {
5568  $returnValue = $inputValue;
5569  }
5570  return $returnValue;
5571  }
5572 
5573  /***********************************************
5574  *
5575  * Link functions (typolink)
5576  *
5577  ***********************************************/
5578 
5593  protected function resolveMixedLinkParameter($linkText, $mixedLinkParameter, &$configuration = [])
5594  {
5595  $linkParameter = null;
5596 
5597  // Link parameter value = first part
5598  $linkParameterParts = GeneralUtility::makeInstance(TypoLinkCodecService::class)->decode($mixedLinkParameter);
5599 
5600  // Check for link-handler keyword
5601  list($linkHandlerKeyword, $linkHandlerValue) = explode(':', $linkParameterParts['url'], 2);
5602  if ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['typolinkLinkHandler'][$linkHandlerKeyword] && (string)$linkHandlerValue !== '') {
5603  $linkHandlerObj = GeneralUtility::getUserObj($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['typolinkLinkHandler'][$linkHandlerKeyword]);
5604  if (method_exists($linkHandlerObj, 'main')) {
5605  return $linkHandlerObj->main($linkText, $configuration, $linkHandlerKeyword, $linkHandlerValue, $mixedLinkParameter, $this);
5606  }
5607  }
5608 
5609  // Resolve FAL-api "file:UID-of-sys_file-record" and "file:combined-identifier"
5610  if ($linkHandlerKeyword === 'file' && strpos($linkParameterParts['url'], 'file://') !== 0) {
5611  try {
5612  $fileOrFolderObject = $this->getResourceFactory()->retrieveFileOrFolderObject($linkHandlerValue);
5613  // Link to a folder or file
5614  if ($fileOrFolderObject instanceof File || $fileOrFolderObject instanceof Folder) {
5615  $linkParameter = $fileOrFolderObject->getPublicUrl();
5616  } else {
5617  $linkParameter = null;
5618  }
5619  } catch (\RuntimeException $e) {
5620  // Element wasn't found
5621  $linkParameter = null;
5622  } catch (ResourceDoesNotExistException $e) {
5623  // Resource was not found
5624  return $linkText;
5625  }
5626  } elseif (in_array(strtolower(preg_replace('#\s|[[:cntrl:]]#', '', $linkHandlerKeyword)), ['javascript', 'data'], true)) {
5627  // Disallow insecure scheme's like javascript: or data:
5628  return $linkText;
5629  } else {
5630  $linkParameter = $linkParameterParts['url'];
5631  }
5632 
5633  // additional parameters that need to be set
5634  if ($linkParameterParts['additionalParams'] !== '') {
5635  $forceParams = $linkParameterParts['additionalParams'];
5636  // params value
5637  $configuration['additionalParams'] .= $forceParams[0] === '&' ? $forceParams : '&' . $forceParams;
5638  }
5639 
5640  return [
5641  'href' => $linkParameter,
5642  'target' => $linkParameterParts['target'],
5643  'class' => $linkParameterParts['class'],
5644  'title' => $linkParameterParts['title']
5645  ];
5646  }
5647 
5662  public function typoLink($linkText, $conf)
5663  {
5664  $linkText = (string)$linkText;
5665  $tsfe = $this->getTypoScriptFrontendController();
5666 
5667  $linkParameter = trim(isset($conf['parameter.']) ? $this->stdWrap($conf['parameter'], $conf['parameter.']) : $conf['parameter']);
5668  $this->lastTypoLinkUrl = '';
5669  $this->lastTypoLinkTarget = '';
5670 
5671  $resolvedLinkParameters = $this->resolveMixedLinkParameter($linkText, $linkParameter, $conf);
5672  // check if the link handler hook has resolved the link completely already
5673  if (!is_array($resolvedLinkParameters)) {
5674  return $resolvedLinkParameters;
5675  }
5676  $linkParameter = $resolvedLinkParameters['href'];
5677  $target = $resolvedLinkParameters['target'];
5678  $title = $resolvedLinkParameters['title'];
5679 
5680  if (!$linkParameter) {
5681  return $linkText;
5682  }
5683 
5684  // Detecting kind of link and resolve all necessary parameters
5685  $linkService = GeneralUtility::makeInstance(LinkService::class);
5686  try {
5687  $linkDetails = $linkService->resolve($linkParameter);
5688  } catch (Exception\InvalidPathException $exception) {
5689  $logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
5690  $logger->warning('The link could not be generated', ['exception' => $exception]);
5691 
5692  return $linkText;
5693  }
5694 
5695  $linkDetails['typoLinkParameter'] = $linkParameter;
5696  if (isset($linkDetails['type']) && isset($GLOBALS['TYPO3_CONF_VARS']['FE']['typolinkBuilder'][$linkDetails['type']])) {
5698  $linkBuilder = GeneralUtility::makeInstance(
5699  $GLOBALS['TYPO3_CONF_VARS']['FE']['typolinkBuilder'][$linkDetails['type']],
5700  $this
5701  );
5702  try {
5703  list($this->lastTypoLinkUrl, $linkText, $target) = $linkBuilder->build($linkDetails, $linkText, $target, $conf);
5704  } catch (UnableToLinkException $e) {
5706  $logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
5707  $logger->debug(sprintf('Unable to link "%s": %s', $e->getLinkText(), $e->getMessage()), ['exception' => $e]);
5708 
5709  // Only return the link text directly
5710  return $e->getLinkText();
5711  }
5712  } elseif (isset($linkDetails['url'])) {
5713  $this->lastTypoLinkUrl = $linkDetails['url'];
5714  } else {
5715  return $linkText;
5716  }
5717 
5718  $finalTagParts = [
5719  'aTagParams' => $this->getATagParams($conf) . $this->extLinkATagParams($this->lastTypoLinkUrl, $linkDetails['type']),
5720  'url' => $this->lastTypoLinkUrl,
5721  'TYPE' => $linkDetails['type']
5722  ];
5723 
5724  // Ensure "href" is not in the list of aTagParams to avoid double tags, usually happens within buggy parseFunc settings
5725  if (!empty($finalTagParts['aTagParams'])) {
5726  $aTagParams = GeneralUtility::get_tag_attributes($finalTagParts['aTagParams']);
5727  if (isset($aTagParams['href'])) {
5728  unset($aTagParams['href']);
5729  $finalTagParts['aTagParams'] = GeneralUtility::implodeAttributes($aTagParams);
5730  }
5731  }
5732 
5733  // Building the final <a href=".."> tag
5734  $tagAttributes = [];
5735 
5736  // Title attribute
5737  if (empty($title)) {
5738  $title = $conf['title'];
5739  if ($conf['title.']) {
5740  $title = $this->stdWrap($title, $conf['title.']);
5741  }
5742  }
5743 
5744  // Check, if the target is coded as a JS open window link:
5745  $JSwindowParts = [];
5746  $JSwindowParams = '';
5747  if ($target && preg_match('/^([0-9]+)x([0-9]+)(:(.*)|.*)$/', $target, $JSwindowParts)) {
5748  // Take all pre-configured and inserted parameters and compile parameter list, including width+height:
5749  $JSwindow_tempParamsArr = GeneralUtility::trimExplode(',', strtolower($conf['JSwindow_params'] . ',' . $JSwindowParts[4]), true);
5750  $JSwindow_paramsArr = [];
5751  foreach ($JSwindow_tempParamsArr as $JSv) {
5752  list($JSp, $JSv) = explode('=', $JSv, 2);
5753  $JSwindow_paramsArr[$JSp] = $JSp . '=' . $JSv;
5754  }
5755  // Add width/height:
5756  $JSwindow_paramsArr['width'] = 'width=' . $JSwindowParts[1];
5757  $JSwindow_paramsArr['height'] = 'height=' . $JSwindowParts[2];
5758  // Imploding into string:
5759  $JSwindowParams = implode(',', $JSwindow_paramsArr);
5760  }
5761  if (!$JSwindowParams && $linkDetails['type'] === LinkService::TYPE_EMAIL && $tsfe->spamProtectEmailAddresses === 'ascii') {
5762  $tagAttributes['href'] = $finalTagParts['url'];
5763  } else {
5764  $tagAttributes['href'] = htmlspecialchars($finalTagParts['url']);
5765  }
5766  if (!empty($title)) {
5767  $tagAttributes['title'] = htmlspecialchars($title);
5768  }
5769 
5770  // Target attribute
5771  if (!empty($target)) {
5772  $tagAttributes['target'] = htmlspecialchars($target);
5773  } elseif ($JSwindowParams && !in_array($tsfe->xhtmlDoctype, ['xhtml_strict', 'xhtml_11'], true)) {
5774  // Create TARGET-attribute only if the right doctype is used
5775  $tagAttributes['target'] = 'FEopenLink';
5776  }
5777 
5778  if ($JSwindowParams) {
5779  $onClick = 'vHWin=window.open(' . GeneralUtility::quoteJSvalue($tsfe->baseUrlWrap($finalTagParts['url'])) . ',\'FEopenLink\',' . GeneralUtility::quoteJSvalue($JSwindowParams) . ');vHWin.focus();return false;';
5780  $tagAttributes['onclick'] = htmlspecialchars($onClick);
5781  }
5782 
5783  if (!empty($resolvedLinkParameters['class'])) {
5784  $tagAttributes['class'] = htmlspecialchars($resolvedLinkParameters['class']);
5785  }
5786 
5787  // Prevent trouble with double and missing spaces between attributes and merge params before implode
5788  $finalTagAttributes = array_merge($tagAttributes, GeneralUtility::get_tag_attributes($finalTagParts['aTagParams']));
5789  $finalAnchorTag = '<a ' . GeneralUtility::implodeAttributes($finalTagAttributes) . '>';
5790 
5791  if (!empty($finalTagParts['aTagParams'])) {
5792  $tagAttributes = array_merge($tagAttributes, GeneralUtility::get_tag_attributes($finalTagParts['aTagParams']));
5793  }
5794  // kept for backwards-compatibility in hooks
5795  $finalTagParts['targetParams'] = !empty($tagAttributes['target']) ? ' target="' . $tagAttributes['target'] . '"' : '';
5796  $this->lastTypoLinkTarget = $target;
5797 
5798  // Call user function:
5799  if ($conf['userFunc']) {
5800  $finalTagParts['TAG'] = $finalAnchorTag;
5801  $finalAnchorTag = $this->callUserFunction($conf['userFunc'], $conf['userFunc.'], $finalTagParts);
5802  }
5803 
5804  // Hook: Call post processing function for link rendering:
5805  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'])) {
5806  $_params = [
5807  'conf' => &$conf,
5808  'linktxt' => &$linkText,
5809  'finalTag' => &$finalAnchorTag,
5810  'finalTagParts' => &$finalTagParts,
5811  'linkDetails' => &$linkDetails,
5812  'tagAttributes' => &$tagAttributes
5813  ];
5814  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['typoLink_PostProc'] as $_funcRef) {
5815  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
5816  }
5817  }
5818 
5819  // If flag "returnLastTypoLinkUrl" set, then just return the latest URL made:
5820  if ($conf['returnLast']) {
5821  switch ($conf['returnLast']) {
5822  case 'url':
5823  return $this->lastTypoLinkUrl;
5824  break;
5825  case 'target':
5827  break;
5828  }
5829  }
5830 
5831  $wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
5832 
5833  if ($conf['ATagBeforeWrap']) {
5834  return $finalAnchorTag . $this->wrap($linkText, $wrap) . '</a>';
5835  }
5836  return $this->wrap($finalAnchorTag . $linkText . '</a>', $wrap);
5837  }
5838 
5846  public function typoLink_URL($conf)
5847  {
5848  $this->typoLink('|', $conf);
5849  return $this->lastTypoLinkUrl;
5850  }
5851 
5865  public function getTypoLink($label, $params, $urlParameters = [], $target = '')
5866  {
5867  $conf = [];
5868  $conf['parameter'] = $params;
5869  if ($target) {
5870  $conf['target'] = $target;
5871  $conf['extTarget'] = $target;
5872  $conf['fileTarget'] = $target;
5873  }
5874  if (is_array($urlParameters)) {
5875  if (!empty($urlParameters)) {
5876  $conf['additionalParams'] .= GeneralUtility::implodeArrayForUrl('', $urlParameters);
5877  }
5878  } else {
5879  $conf['additionalParams'] .= $urlParameters;
5880  }
5881  $out = $this->typoLink($label, $conf);
5882  return $out;
5883  }
5884 
5892  public function getUrlToCurrentLocation($addQueryString = true)
5893  {
5894  $conf = [];
5895  $conf['parameter'] = $this->getTypoScriptFrontendController()->id . ',' . $this->getTypoScriptFrontendController()->type;
5896  if ($addQueryString) {
5897  $conf['addQueryString'] = '1';
5898  $linkVars = implode(',', array_keys(GeneralUtility::explodeUrl2Array($this->getTypoScriptFrontendController()->linkVars)));
5899  $conf['addQueryString.'] = [
5900  'method' => 'GET',
5901  'exclude' => 'id,type,cHash' . ($linkVars ? ',' . $linkVars : '')
5902  ];
5903  $conf['useCacheHash'] = GeneralUtility::_GET('cHash') ? '1' : '0';
5904  }
5905 
5906  return $this->typoLink_URL($conf);
5907  }
5908 
5918  public function getTypoLink_URL($params, $urlParameters = [], $target = '')
5919  {
5920  $this->getTypoLink('', $params, $urlParameters, $target);
5921  return $this->lastTypoLinkUrl;
5922  }
5923 
5931  public function typolinkWrap($conf)
5932  {
5933  $k = md5(microtime());
5934  return explode($k, $this->typoLink($k, $conf));
5935  }
5936 
5945  public function currentPageUrl($urlParameters = [], $id = 0)
5946  {
5947  $tsfe = $this->getTypoScriptFrontendController();
5948  return $this->getTypoLink_URL($id ?: $tsfe->id, $urlParameters, $tsfe->sPre);
5949  }
5950 
5960  protected function processUrl($context, $url, $typolinkConfiguration = [])
5961  {
5962  if (
5963  empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['urlProcessing']['urlProcessors'])
5964  || !is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['urlProcessing']['urlProcessors'])
5965  ) {
5966  return $url;
5967  }
5968 
5969  $urlProcessors = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['urlProcessing']['urlProcessors'];
5970  foreach ($urlProcessors as $identifier => $configuration) {
5971  if (empty($configuration) || !is_array($configuration)) {
5972  throw new \RuntimeException('Missing configuration for URI processor "' . $identifier . '".', 1442050529);
5973  }
5974  if (!is_string($configuration['processor']) || empty($configuration['processor']) || !class_exists($configuration['processor']) || !is_subclass_of($configuration['processor'], UrlProcessorInterface::class)) {
5975  throw new \RuntimeException('The URI processor "' . $identifier . '" defines an invalid provider. Ensure the class exists and implements the "' . UrlProcessorInterface::class . '".', 1442050579);
5976  }
5977  }
5978 
5979  $orderedProcessors = GeneralUtility::makeInstance(DependencyOrderingService::class)->orderByDependencies($urlProcessors);
5980  $keepProcessing = true;
5981 
5982  foreach ($orderedProcessors as $configuration) {
5984  $urlProcessor = GeneralUtility::makeInstance($configuration['processor']);
5985  $url = $urlProcessor->process($context, $url, $typolinkConfiguration, $this, $keepProcessing);
5986  if (!$keepProcessing) {
5987  break;
5988  }
5989  }
5990 
5991  return $url;
5992  }
5993 
6003  public function getClosestMPvalueForPage($pageId, $raw = false)
6004  {
6005  $tsfe = $this->getTypoScriptFrontendController();
6006  if (empty($GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) || !$tsfe->MP) {
6007  return '';
6008  }
6009  // MountPoints:
6010  $MP = '';
6011  // Same page as current.
6012  if ((int)$tsfe->id === (int)$pageId) {
6013  $MP = $tsfe->MP;
6014  } else {
6015  // ... otherwise find closest meeting point:
6016  // Gets rootline of linked-to page
6017  $tCR_rootline = $tsfe->sys_page->getRootLine($pageId, '', true);
6018  $inverseTmplRootline = array_reverse($tsfe->tmpl->rootLine);
6019  $rl_mpArray = [];
6020  $startMPaccu = false;
6021  // Traverse root line of link uid and inside of that the REAL root line of current position.
6022  foreach ($tCR_rootline as $tCR_data) {
6023  foreach ($inverseTmplRootline as $rlKey => $invTmplRLRec) {
6024  // Force accumulating when in overlay mode: Links to this page have to stay within the current branch
6025  if ($invTmplRLRec['_MOUNT_OL'] && (int)$tCR_data['uid'] === (int)$invTmplRLRec['uid']) {
6026  $startMPaccu = true;
6027  }
6028  // Accumulate MP data:
6029  if ($startMPaccu && $invTmplRLRec['_MP_PARAM']) {
6030  $rl_mpArray[] = $invTmplRLRec['_MP_PARAM'];
6031  }
6032  // If two PIDs matches and this is NOT the site root, start accumulation of MP data (on the next level):
6033  // (The check for site root is done so links to branches outsite the site but sharing the site roots PID
6034  // is NOT detected as within the branch!)
6035  if ((int)$tCR_data['pid'] === (int)$invTmplRLRec['pid'] && count($inverseTmplRootline) !== $rlKey + 1) {
6036  $startMPaccu = true;
6037  }
6038  }
6039  if ($startMPaccu) {
6040  // Good enough...
6041  break;
6042  }
6043  }
6044  if (!empty($rl_mpArray)) {
6045  $MP = implode(',', array_reverse($rl_mpArray));
6046  }
6047  }
6048  return $raw ? $MP : ($MP ? '&MP=' . rawurlencode($MP) : '');
6049  }
6050 
6060  public function getMailTo($mailAddress, $linktxt)
6061  {
6062  $mailAddress = (string)$mailAddress;
6063  if ((string)$linktxt === '') {
6064  $linktxt = htmlspecialchars($mailAddress);
6065  }
6066 
6067  $originalMailToUrl = 'mailto:' . $mailAddress;
6068  $mailToUrl = $this->processUrl(UrlProcessorInterface::CONTEXT_MAIL, $originalMailToUrl);
6069 
6070  // no processing happened, therefore, the default processing kicks in
6071  if ($mailToUrl === $originalMailToUrl) {
6072  $tsfe = $this->getTypoScriptFrontendController();
6073  if ($tsfe->spamProtectEmailAddresses) {
6074  $mailToUrl = $this->encryptEmail($mailToUrl, $tsfe->spamProtectEmailAddresses);
6075  if ($tsfe->spamProtectEmailAddresses !== 'ascii') {
6076  $mailToUrl = 'javascript:linkTo_UnCryptMailto(' . GeneralUtility::quoteJSvalue($mailToUrl) . ');';
6077  }
6078  $atLabel = trim($tsfe->config['config']['spamProtectEmailAddresses_atSubst']) ?: '(at)';
6079  $spamProtectedMailAddress = str_replace('@', $atLabel, htmlspecialchars($mailAddress));
6080  if ($tsfe->config['config']['spamProtectEmailAddresses_lastDotSubst']) {
6081  $lastDotLabel = trim($tsfe->config['config']['spamProtectEmailAddresses_lastDotSubst']);
6082  $lastDotLabel = $lastDotLabel ? $lastDotLabel : '(dot)';
6083  $spamProtectedMailAddress = preg_replace('/\\.([^\\.]+)$/', $lastDotLabel . '$1', $spamProtectedMailAddress);
6084  }
6085  $linktxt = str_ireplace($mailAddress, $spamProtectedMailAddress, $linktxt);
6086  }
6087  }
6088 
6089  return [$mailToUrl, $linktxt];
6090  }
6091 
6099  protected function encryptEmail($string, $type)
6100  {
6101  $out = '';
6102  // obfuscates using the decimal HTML entity references for each character
6103  if ($type === 'ascii') {
6104  $stringLength = strlen($string);
6105  for ($a = 0; $a < $stringLength; $a++) {
6106  $out .= '&#' . ord(substr($string, $a, 1)) . ';';
6107  }
6108  } else {
6109  // like str_rot13() but with a variable offset and a wider character range
6110  $len = strlen($string);
6111  $offset = (int)$type;
6112  for ($i = 0; $i < $len; $i++) {
6113  $charValue = ord($string[$i]);
6114  // 0-9 . , - + / :
6115  if ($charValue >= 43 && $charValue <= 58) {
6116  $out .= $this->encryptCharcode($charValue, 43, 58, $offset);
6117  } elseif ($charValue >= 64 && $charValue <= 90) {
6118  // A-Z @
6119  $out .= $this->encryptCharcode($charValue, 64, 90, $offset);
6120  } elseif ($charValue >= 97 && $charValue <= 122) {
6121  // a-z
6122  $out .= $this->encryptCharcode($charValue, 97, 122, $offset);
6123  } else {
6124  $out .= $string[$i];
6125  }
6126  }
6127  }
6128  return $out;
6129  }
6130 
6138  protected function decryptEmail($string, $type)
6139  {
6140  $out = '';
6141  // obfuscates using the decimal HTML entity references for each character
6142  if ($type === 'ascii') {
6143  $stringLength = strlen($string);
6144  for ($a = 0; $a < $stringLength; $a++) {
6145  $out .= '&#' . ord(substr($string, $a, 1)) . ';';
6146  }
6147  } else {
6148  // like str_rot13() but with a variable offset and a wider character range
6149  $len = strlen($string);
6150  $offset = (int)$type * -1;
6151  for ($i = 0; $i < $len; $i++) {
6152  $charValue = ord($string[$i]);
6153  // 0-9 . , - + / :
6154  if ($charValue >= 43 && $charValue <= 58) {
6155  $out .= $this->encryptCharcode($charValue, 43, 58, $offset);
6156  } elseif ($charValue >= 64 && $charValue <= 90) {
6157  // A-Z @
6158  $out .= $this->encryptCharcode($charValue, 64, 90, $offset);
6159  } elseif ($charValue >= 97 && $charValue <= 122) {
6160  // a-z
6161  $out .= $this->encryptCharcode($charValue, 97, 122, $offset);
6162  } else {
6163  $out .= $string[$i];
6164  }
6165  }
6166  }
6167  return $out;
6168  }
6169 
6180  protected function encryptCharcode($n, $start, $end, $offset)
6181  {
6182  $n = $n + $offset;
6183  if ($offset > 0 && $n > $end) {
6184  $n = $start + ($n - $end - 1);
6185  } elseif ($offset < 0 && $n < $start) {
6186  $n = $end - ($start - $n - 1);
6187  }
6188  return chr($n);
6189  }
6190 
6200  public function getQueryArguments($conf, $overruleQueryArguments = [], $forceOverruleArguments = false)
6201  {
6202  switch ((string)$conf['method']) {
6203  case 'GET':
6204  $currentQueryArray = GeneralUtility::_GET();
6205  break;
6206  case 'POST':
6207  $currentQueryArray = GeneralUtility::_POST();
6208  break;
6209  case 'GET,POST':
6210  $currentQueryArray = GeneralUtility::_GET();
6212  break;
6213  case 'POST,GET':
6214  $currentQueryArray = GeneralUtility::_POST();
6216  break;
6217  default:
6218  $currentQueryArray = GeneralUtility::explodeUrl2Array($this->getEnvironmentVariable('QUERY_STRING'), true);
6219  }
6220  if ($conf['exclude']) {
6221  $exclude = str_replace(',', '&', $conf['exclude']);
6222  $exclude = GeneralUtility::explodeUrl2Array($exclude, true);
6223  // never repeat id
6224  $exclude['id'] = 0;
6225  $newQueryArray = ArrayUtility::arrayDiffAssocRecursive($currentQueryArray, $exclude);
6226  } else {
6227  $newQueryArray = $currentQueryArray;
6228  }
6229  if ($forceOverruleArguments) {
6230  ArrayUtility::mergeRecursiveWithOverrule($newQueryArray, $overruleQueryArguments);
6231  } else {
6232  ArrayUtility::mergeRecursiveWithOverrule($newQueryArray, $overruleQueryArguments, false);
6233  }
6234  return GeneralUtility::implodeArrayForUrl('', $newQueryArray, '', false, true);
6235  }
6236 
6237  /***********************************************
6238  *
6239  * Miscellaneous functions, stand alone
6240  *
6241  ***********************************************/
6253  public function wrap($content, $wrap, $char = '|')
6254  {
6255  if ($wrap) {
6256  $wrapArr = explode($char, $wrap);
6257  $content = trim($wrapArr[0]) . $content . trim($wrapArr[1]);
6258  }
6259  return $content;
6260  }
6261 
6272  public function noTrimWrap($content, $wrap, $char = '|')
6273  {
6274  if ($wrap) {
6275  // expects to be wrapped with (at least) 3 characters (before, middle, after)
6276  // anything else is not taken into account
6277  $wrapArr = explode($char, $wrap, 4);
6278  $content = $wrapArr[1] . $content . $wrapArr[2];
6279  }
6280  return $content;
6281  }
6282 
6291  public function wrapSpace($content, $wrap, array $conf = null)
6292  {
6293  if (trim($wrap)) {
6294  $wrapArray = explode('|', $wrap);
6295  $wrapBefore = (int)$wrapArray[0];
6296  $wrapAfter = (int)$wrapArray[1];
6297  $useDivTag = isset($conf['useDiv']) && $conf['useDiv'];
6298  if ($wrapBefore) {
6299  if ($useDivTag) {
6300  $content = '<div class="content-spacer spacer-before" style="height:' . $wrapBefore . 'px;"></div>' . $content;
6301  } else {
6302  $content = '<span style="width: 1px; height: ' . $wrapBefore . 'px; display: inline-block;"></span><br />' . $content;
6303  }
6304  }
6305  if ($wrapAfter) {
6306  if ($useDivTag) {
6307  $content .= '<div class="content-spacer spacer-after" style="height:' . $wrapAfter . 'px;"></div>';
6308  } else {
6309  $content .= '<span style="width: 1px; height: ' . $wrapAfter . 'px; display: inline-block;"></span><br />';
6310  }
6311  }
6312  }
6313  return $content;
6314  }
6315 
6326  public function callUserFunction($funcName, $conf, $content)
6327  {
6328  // Split parts
6329  $parts = explode('->', $funcName);
6330  if (count($parts) === 2) {
6331  // Check whether PHP class is available
6332  if (class_exists($parts[0])) {
6333  $classObj = GeneralUtility::makeInstance($parts[0]);
6334  if (is_object($classObj) && method_exists($classObj, $parts[1])) {
6335  $classObj->cObj = $this;
6336  $content = call_user_func_array([
6337  $classObj,
6338  $parts[1]
6339  ], [
6340  $content,
6341  $conf
6342  ]);
6343  } else {
6344  $this->getTimeTracker()->setTSlogMessage('Method "' . $parts[1] . '" did not exist in class "' . $parts[0] . '"', 3);
6345  }
6346  } else {
6347  $this->getTimeTracker()->setTSlogMessage('Class "' . $parts[0] . '" did not exist', 3);
6348  }
6349  } elseif (function_exists($funcName)) {
6350  $content = call_user_func($funcName, $content, $conf);
6351  } else {
6352  $this->getTimeTracker()->setTSlogMessage('Function "' . $funcName . '" did not exist', 3);
6353  }
6354  return $content;
6355  }
6356 
6365  public function processParams($params)
6366  {
6368  $paramArr = [];
6369  $lines = GeneralUtility::trimExplode(LF, $params, true);
6370  foreach ($lines as $val) {
6371  $pair = explode('=', $val, 2);
6372  $key = trim($pair[0]);
6373  if ($key[0] !== '#' && $key[0] !== '/') {
6374  $paramArr[$key] = trim($pair[1]);
6375  }
6376  }
6377  return $paramArr;
6378  }
6379 
6386  public function keywords($content)
6387  {
6388  $listArr = preg_split('/[,;' . LF . ']/', $content);
6389  foreach ($listArr as $k => $v) {
6390  $listArr[$k] = trim($v);
6391  }
6392  return implode(',', $listArr);
6393  }
6394 
6403  public function caseshift($theValue, $case)
6404  {
6405  switch (strtolower($case)) {
6406  case 'upper':
6407  $theValue = mb_strtoupper($theValue, 'utf-8');
6408  break;
6409  case 'lower':
6410  $theValue = mb_strtolower($theValue, 'utf-8');
6411  break;
6412  case 'capitalize':
6413  $theValue = mb_convert_case($theValue, MB_CASE_TITLE, 'utf-8');
6414  break;
6415  case 'ucfirst':
6416  $firstChar = mb_substr($theValue, 0, 1, 'utf-8');
6417  $firstChar = mb_strtoupper($firstChar, 'utf-8');
6418  $remainder = mb_substr($theValue, 1, null, 'utf-8');
6419  $theValue = $firstChar . $remainder;
6420  break;
6421  case 'lcfirst':
6422  $firstChar = mb_substr($theValue, 0, 1, 'utf-8');
6423  $firstChar = mb_strtolower($firstChar, 'utf-8');
6424  $remainder = mb_substr($theValue, 1, null, 'utf-8');
6425  $theValue = $firstChar . $remainder;
6426  break;
6427  case 'uppercamelcase':
6428  $theValue = GeneralUtility::underscoredToUpperCamelCase($theValue);
6429  break;
6430  case 'lowercamelcase':
6431  $theValue = GeneralUtility::underscoredToLowerCamelCase($theValue);
6432  break;
6433  }
6434  return $theValue;
6435  }
6436 
6445  public function HTMLcaseshift($theValue, $case)
6446  {
6447  $inside = 0;
6448  $newVal = '';
6449  $pointer = 0;
6450  $totalLen = strlen($theValue);
6451  do {
6452  if (!$inside) {
6453  $len = strcspn(substr($theValue, $pointer), '<');
6454  $newVal .= $this->caseshift(substr($theValue, $pointer, $len), $case);
6455  $inside = 1;
6456  } else {
6457  $len = strcspn(substr($theValue, $pointer), '>') + 1;
6458  $newVal .= substr($theValue, $pointer, $len);
6459  $inside = 0;
6460  }
6461  $pointer += $len;
6462  } while ($pointer < $totalLen);
6463  return $newVal;
6464  }
6465 
6473  public function calcAge($seconds, $labels)
6474  {
6476  $labels = ' min| hrs| days| yrs| min| hour| day| year';
6477  } else {
6478  $labels = str_replace('"', '', $labels);
6479  }
6480  $labelArr = explode('|', $labels);
6481  if (count($labelArr) === 4) {
6482  $labelArr = array_merge($labelArr, $labelArr);
6483  }
6484  $absSeconds = abs($seconds);
6485  $sign = $seconds > 0 ? 1 : -1;
6486  if ($absSeconds < 3600) {
6487  $val = round($absSeconds / 60);
6488  $seconds = $sign * $val . ($val == 1 ? $labelArr[4] : $labelArr[0]);
6489  } elseif ($absSeconds < 24 * 3600) {
6490  $val = round($absSeconds / 3600);
6491  $seconds = $sign * $val . ($val == 1 ? $labelArr[5] : $labelArr[1]);
6492  } elseif ($absSeconds < 365 * 24 * 3600) {
6493  $val = round($absSeconds / (24 * 3600));
6494  $seconds = $sign * $val . ($val == 1 ? $labelArr[6] : $labelArr[2]);
6495  } else {
6496  $val = round($absSeconds / (365 * 24 * 3600));
6497  $seconds = $sign * $val . ($val == 1 ? $labelArr[7] : $labelArr[3]);
6498  }
6499  return $seconds;
6500  }
6501 
6513  public function sendNotifyEmail($message, $recipients, $cc, $senderAddress, $senderName = '', $replyTo = '')
6514  {
6516  $mail = GeneralUtility::makeInstance(MailMessage::class);
6517  $senderName = trim($senderName);
6518  $senderAddress = trim($senderAddress);
6519  if ($senderName !== '' && $senderAddress !== '') {
6520  $mail->setFrom([$senderAddress => $senderName]);
6521  } elseif ($senderAddress !== '') {
6522  $mail->setFrom([$senderAddress]);
6523  }
6524  $parsedReplyTo = MailUtility::parseAddresses($replyTo);
6525  if (!empty($parsedReplyTo)) {
6526  $mail->setReplyTo($parsedReplyTo);
6527  }
6528  $message = trim($message);
6529  if ($message !== '') {
6530  // First line is subject
6531  $messageParts = explode(LF, $message, 2);
6532  $subject = trim($messageParts[0]);
6533  $plainMessage = trim($messageParts[1]);
6534  $parsedRecipients = MailUtility::parseAddresses($recipients);
6535  if (!empty($parsedRecipients)) {
6536  $mail->setTo($parsedRecipients)
6537  ->setSubject($subject)
6538  ->setBody($plainMessage);
6539  $mail->send();
6540  }
6541  $parsedCc = MailUtility::parseAddresses($cc);
6542  if (!empty($parsedCc)) {
6543  $from = $mail->getFrom();
6545  $mail = GeneralUtility::makeInstance(MailMessage::class);
6546  if (!empty($parsedReplyTo)) {
6547  $mail->setReplyTo($parsedReplyTo);
6548  }
6549  $mail->setFrom($from)
6550  ->setTo($parsedCc)
6551  ->setSubject($subject)
6552  ->setBody($plainMessage);
6553  $mail->send();
6554  }
6555  return true;
6556  }
6557  return false;
6558  }
6559 
6568  public function URLqMark($url, $params)
6569  {
6571  if ($params && !strstr($url, '?')) {
6572  return $url . '?' . $params;
6573  }
6574  return $url . $params;
6575  }
6576 
6586  public function clearTSProperties($TSArr, $propList)
6587  {
6589  $list = explode(',', $propList);
6590  foreach ($list as $prop) {
6591  $prop = trim($prop);
6592  unset($TSArr[$prop]);
6593  unset($TSArr[$prop . '.']);
6594  }
6595  return $TSArr;
6596  }
6597 
6606  public function mergeTSRef($confArr, $prop)
6607  {
6608  if ($confArr[$prop][0] === '<') {
6609  $key = trim(substr($confArr[$prop], 1));
6610  $cF = GeneralUtility::makeInstance(TypoScriptParser::class);
6611  // $name and $conf is loaded with the referenced values.
6612  $old_conf = $confArr[$prop . '.'];
6613  list(, $conf) = $cF->getVal($key, $this->getTypoScriptFrontendController()->tmpl->setup);
6614  if (is_array($old_conf) && !empty($old_conf)) {
6615  $conf = is_array($conf) ? array_replace_recursive($conf, $old_conf) : $old_conf;
6616  }
6617  $confArr[$prop . '.'] = $conf;
6618  }
6619  return $confArr;
6620  }
6621 
6631  public function gifBuilderTextBox($gifbuilderConf, $conf, $text)
6632  {
6634  $chars = (int)$conf['chars'] ?: 20;
6635  $lineDist = (int)$conf['lineDist'] ?: 20;
6636  $Valign = strtolower(trim($conf['Valign']));
6637  $tmplObjNumber = (int)$conf['tmplObjNumber'];
6638  $maxLines = (int)$conf['maxLines'];
6639  if ($tmplObjNumber && $gifbuilderConf[$tmplObjNumber] === 'TEXT') {
6640  $textArr = $this->linebreaks($text, $chars, $maxLines);
6641  $angle = (int)$gifbuilderConf[$tmplObjNumber . '.']['angle'];
6642  foreach ($textArr as $c => $textChunk) {
6643  $index = $tmplObjNumber + 1 + $c * 2;
6644  // Workarea
6645  $gifbuilderConf = $this->clearTSProperties($gifbuilderConf, $index);
6646  $rad_angle = 2 * pi() / 360 * $angle;
6647  $x_d = sin($rad_angle) * $lineDist;
6648  $y_d = cos($rad_angle) * $lineDist;
6649  $diff_x_d = 0;
6650  $diff_y_d = 0;
6651  if ($Valign === 'center') {
6652  $diff_x_d = $x_d * count($textArr);
6653  $diff_x_d = $diff_x_d / 2;
6654  $diff_y_d = $y_d * count($textArr);
6655  $diff_y_d = $diff_y_d / 2;
6656  }
6657  $x_d = round($x_d * $c - $diff_x_d);
6658  $y_d = round($y_d * $c - $diff_y_d);
6659  $gifbuilderConf[$index] = 'WORKAREA';
6660  $gifbuilderConf[$index . '.']['set'] = $x_d . ',' . $y_d;
6661  // Text
6662  $index++;
6663  $gifbuilderConf = $this->clearTSProperties($gifbuilderConf, $index);
6664  $gifbuilderConf[$index] = 'TEXT';
6665  $gifbuilderConf[$index . '.'] = $this->clearTSProperties($gifbuilderConf[$tmplObjNumber . '.'], 'text');
6666  $gifbuilderConf[$index . '.']['text'] = $textChunk;
6667  }
6668  $gifbuilderConf = $this->clearTSProperties($gifbuilderConf, $tmplObjNumber);
6669  }
6670  return $gifbuilderConf;
6671  }
6672 
6684  public function linebreaks($string, $chars, $maxLines = 0)
6685  {
6687  $lines = explode(LF, $string);
6688  $lineArr = [];
6689  $c = 0;
6690  foreach ($lines as $paragraph) {
6691  $words = explode(' ', $paragraph);
6692  foreach ($words as $word) {
6693  if (strlen($lineArr[$c] . $word) > $chars) {
6694  $c++;
6695  }
6696  if (!$maxLines || $c < $maxLines) {
6697  $lineArr[$c] .= $word . ' ';
6698  }
6699  }
6700  $c++;
6701  }
6702  return $lineArr;
6703  }
6704 
6712  public function includeLibs(array $config)
6713  {
6715  $librariesIncluded = false;
6716  if (isset($config['includeLibs']) && $config['includeLibs']) {
6717  $libraries = GeneralUtility::trimExplode(',', $config['includeLibs'], true);
6718  $this->getTypoScriptFrontendController()->includeLibraries($libraries);
6719  $librariesIncluded = true;
6720  }
6721  return $librariesIncluded;
6722  }
6723 
6724  /***********************************************
6725  *
6726  * Database functions, making of queries
6727  *
6728  ***********************************************/
6729 
6745  public function enableFields($table, $show_hidden = false, array $ignore_array = [])
6746  {
6747  $tsfe = $this->getTypoScriptFrontendController();
6748  $show_hidden = $show_hidden ?: ($table === 'pages' ? $tsfe->showHiddenPage : $tsfe->showHiddenRecords);
6749  return $tsfe->sys_page->enableFields($table, (bool)$show_hidden, $ignore_array);
6750  }
6751 
6778  public function getTreeList($id, $depth, $begin = 0, $dontCheckEnableFields = false, $addSelectFields = '', $moreWhereClauses = '', array $prevId_array = [], $recursionLevel = 0)
6779  {
6780  $id = (int)$id;
6781  if (!$id) {
6782  return '';
6783  }
6784 
6785  // Init vars:
6786  $allFields = 'uid,hidden,starttime,endtime,fe_group,extendToSubpages,doktype,php_tree_stop,mount_pid,mount_pid_ol,t3ver_state' . $addSelectFields;
6787  $depth = (int)$depth;
6788  $begin = (int)$begin;
6789  $theList = [];
6790  $addId = 0;
6791  $requestHash = '';
6792 
6793  // First level, check id (second level, this is done BEFORE the recursive call)
6794  $tsfe = $this->getTypoScriptFrontendController();
6795  if (!$recursionLevel) {
6796  // Check tree list cache
6797  // First, create the hash for this request - not sure yet whether we need all these parameters though
6798  $parameters = [
6799  $id,
6800  $depth,
6801  $begin,
6802  $dontCheckEnableFields,
6803  $addSelectFields,
6804  $moreWhereClauses,
6805  $prevId_array,
6806  $tsfe->gr_list
6807  ];
6808  $requestHash = md5(serialize($parameters));
6809  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
6810  ->getQueryBuilderForTable('cache_treelist');
6811  $cacheEntry = $queryBuilder->select('treelist')
6812  ->from('cache_treelist')
6813  ->where(
6814  $queryBuilder->expr()->eq(
6815  'md5hash',
6816  $queryBuilder->createNamedParameter($requestHash, \PDO::PARAM_STR)
6817  ),
6818  $queryBuilder->expr()->orX(
6819  $queryBuilder->expr()->gt(
6820  'expires',
6821  $queryBuilder->createNamedParameter($GLOBALS['EXEC_TIME'], \PDO::PARAM_INT)
6822  ),
6823  $queryBuilder->expr()->eq('expires', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
6824  )
6825  )
6826  ->setMaxResults(1)
6827  ->execute()
6828  ->fetch();
6829 
6830  if (is_array($cacheEntry)) {
6831  // Cache hit
6832  return $cacheEntry['treelist'];
6833  }
6834  // If Id less than zero it means we should add the real id to list:
6835  if ($id < 0) {
6836  $addId = $id = abs($id);
6837  }
6838  // Check start page:
6839  if ($tsfe->sys_page->getRawRecord('pages', $id, 'uid')) {
6840  // Find mount point if any:
6841  $mount_info = $tsfe->sys_page->getMountPointInfo($id);
6842  if (is_array($mount_info)) {
6843  $id = $mount_info['mount_pid'];
6844  // In Overlay mode, use the mounted page uid as added ID!:
6845  if ($addId && $mount_info['overlay']) {
6846  $addId = $id;
6847  }
6848  }
6849  } else {
6850  // Return blank if the start page was NOT found at all!
6851  return '';
6852  }
6853  }
6854  // Add this ID to the array of IDs
6855  if ($begin <= 0) {
6856  $prevId_array[] = $id;
6857  }
6858  // Select sublevel:
6859  if ($depth > 0) {
6860  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
6861  $queryBuilder->getRestrictions()
6862  ->removeAll()
6863  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
6864  $queryBuilder->select(...GeneralUtility::trimExplode(',', $allFields, true))
6865  ->from('pages')
6866  ->where(
6867  $queryBuilder->expr()->eq(
6868  'pid',
6869  $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
6870  )
6871  )
6872  ->orderBy('sorting');
6873 
6874  if (!empty($moreWhereClauses)) {
6875  $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($moreWhereClauses));
6876  }
6877 
6878  $result = $queryBuilder->execute();
6879  while ($row = $result->fetch()) {
6881  $versionState = VersionState::cast($row['t3ver_state']);
6882  $tsfe->sys_page->versionOL('pages', $row);
6883  if ((int)$row['doktype'] === PageRepository::DOKTYPE_RECYCLER
6884  || (int)$row['doktype'] === PageRepository::DOKTYPE_BE_USER_SECTION
6885  || $versionState->indicatesPlaceholder()
6886  ) {
6887  // Doing this after the overlay to make sure changes
6888  // in the overlay are respected.
6889  // However, we do not process pages below of and
6890  // including of type recycler and BE user section
6891  continue;
6892  }
6893  // Find mount point if any:
6894  $next_id = $row['uid'];
6895  $mount_info = $tsfe->sys_page->getMountPointInfo($next_id, $row);
6896  // Overlay mode:
6897  if (is_array($mount_info) && $mount_info['overlay']) {
6898  $next_id = $mount_info['mount_pid'];
6899  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
6900  ->getQueryBuilderForTable('pages');
6901  $queryBuilder->getRestrictions()
6902  ->removeAll()
6903  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
6904  $queryBuilder->select(...GeneralUtility::trimExplode(',', $allFields, true))
6905  ->from('pages')
6906  ->where(
6907  $queryBuilder->expr()->eq(
6908  'uid',
6909  $queryBuilder->createNamedParameter($next_id, \PDO::PARAM_INT)
6910  )
6911  )
6912  ->orderBy('sorting')
6913  ->setMaxResults(1);
6914 
6915  if (!empty($moreWhereClauses)) {
6916  $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($moreWhereClauses));
6917  }
6918 
6919  $row = $queryBuilder->execute()->fetch();
6920  $tsfe->sys_page->versionOL('pages', $row);
6921  if ((int)$row['doktype'] === PageRepository::DOKTYPE_RECYCLER
6922  || (int)$row['doktype'] === PageRepository::DOKTYPE_BE_USER_SECTION
6923  || $versionState->indicatesPlaceholder()
6924  ) {
6925  // Doing this after the overlay to make sure
6926  // changes in the overlay are respected.
6927  // see above
6928  continue;
6929  }
6930  }
6931  // Add record:
6932  if ($dontCheckEnableFields || $tsfe->checkPagerecordForIncludeSection($row)) {
6933  // Add ID to list:
6934  if ($begin <= 0) {
6935  if ($dontCheckEnableFields || $tsfe->checkEnableFields($row)) {
6936  $theList[] = $next_id;
6937  }
6938  }
6939  // Next level:
6940  if ($depth > 1 && !$row['php_tree_stop']) {
6941  // Normal mode:
6942  if (is_array($mount_info) && !$mount_info['overlay']) {
6943  $next_id = $mount_info['mount_pid'];
6944  }
6945  // Call recursively, if the id is not in prevID_array:
6946  if (!in_array($next_id, $prevId_array)) {
6947  $theList = array_merge(
6949  ',',
6950  $this->getTreeList(
6951  $next_id,
6952  $depth - 1,
6953  $begin - 1,
6954  $dontCheckEnableFields,
6955  $addSelectFields,
6956  $moreWhereClauses,
6957  $prevId_array,
6958  $recursionLevel + 1
6959  ),
6960  true
6961  ),
6962  $theList
6963  );
6964  }
6965  }
6966  }
6967  }
6968  }
6969  // If first run, check if the ID should be returned:
6970  if (!$recursionLevel) {
6971  if ($addId) {
6972  if ($begin > 0) {
6973  $theList[] = 0;
6974  } else {
6975  $theList[] = $addId;
6976  }
6977  }
6978 
6979  $cacheEntry = [
6980  'md5hash' => $requestHash,
6981  'pid' => $id,
6982  'treelist' => implode(',', $theList),
6983  'tstamp' => $GLOBALS['EXEC_TIME'],
6984  ];
6985 
6986  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('cache_treelist');
6987  try {
6988  $connection->transactional(function ($connection) use ($cacheEntry) {
6989  $connection->insert('cache_treelist', $cacheEntry);
6990  });
6991  } catch (\Throwable $e) {
6992  }
6993  }
6994 
6995  return implode(',', $theList);
6996  }
6997 
7007  public function searchWhere($searchWords, $searchFieldList, $searchTable = '')
7008  {
7009  if (!$searchWords) {
7010  return ' AND 1=1';
7011  }
7012 
7013  if (empty($searchTable)) {
7015  'Parameter 3 of ContentObjectRenderer::searchWhere() is required can not be omitted anymore. Using Default connection!'
7016  );
7017  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
7018  ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME)
7019  ->createQueryBuilder();
7020  } else {
7021  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
7022  ->getQueryBuilderForTable($searchTable);
7023  }
7024 
7025  $prefixTableName = $searchTable ? $searchTable . '.' : '';
7026 
7027  $where = $queryBuilder->expr()->andX();
7028  $searchFields = explode(',', $searchFieldList);
7029  $searchWords = preg_split('/[ ,]/', $searchWords);
7030  foreach ($searchWords as $searchWord) {
7031  $searchWord = trim($searchWord);
7032  if (strlen($searchWord) < 3) {
7033  continue;
7034  }
7035  $searchWordConstraint = $queryBuilder->expr()->orX();
7036  $searchWord = $queryBuilder->escapeLikeWildcards($searchWord);
7037  foreach ($searchFields as $field) {
7038  $searchWordConstraint->add(
7039  $queryBuilder->expr()->like($prefixTableName . $field, $queryBuilder->quote('%' . $searchWord . '%'))
7040  );
7041  }
7042 
7043  if ($searchWordConstraint->count()) {
7044  $where->add($searchWordConstraint);
7045  }
7046  }
7047 
7048  return ' AND ' . (string)$where;
7049  }
7050 
7060  public function exec_getQuery($table, $conf)
7061  {
7062  $statement = $this->getQuery($table, $conf);
7063  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
7064 
7065  return $connection->executeQuery($statement);
7066  }
7067 
7077  public function getRecords($tableName, array $queryConfiguration)
7078  {
7079  $records = [];
7080 
7081  $statement = $this->exec_getQuery($tableName, $queryConfiguration);
7082 
7083  $tsfe = $this->getTypoScriptFrontendController();
7084  while ($row = $statement->fetch()) {
7085  // Versioning preview:
7086  $tsfe->sys_page->versionOL($tableName, $row, true);
7087 
7088  // Language overlay:
7089  if (is_array($row) && $tsfe->sys_language_contentOL) {
7090  if ($tableName === 'pages') {
7091  $row = $tsfe->sys_page->getPageOverlay($row);
7092  } else {
7093  $row = $tsfe->sys_page->getRecordOverlay(
7094  $tableName,
7095  $row,
7096  $tsfe->sys_language_content,
7097  $tsfe->sys_language_contentOL
7098  );
7099  }
7100  }
7101 
7102  // Might be unset in the sys_language_contentOL
7103  if (is_array($row)) {
7104  $records[] = $row;
7105  }
7106  }
7107 
7108  return $records;
7109  }
7110 
7124  public function getQuery($table, $conf, $returnQueryArray = false)
7125  {
7126  // Resolve stdWrap in these properties first
7127  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
7128  $properties = [
7129  'pidInList',
7130  'uidInList',
7131  'languageField',
7132  'selectFields',
7133  'max',
7134  'begin',
7135  'groupBy',
7136  'orderBy',
7137  'join',
7138  'leftjoin',
7139  'rightjoin',
7140  'recursive',
7141  'where'
7142  ];
7143  foreach ($properties as $property) {
7144  $conf[$property] = trim(
7145  isset($conf[$property . '.'])
7146  ? $this->stdWrap($conf[$property], $conf[$property . '.'])
7147  : $conf[$property]
7148  );
7149  if ($conf[$property] === '') {
7150  unset($conf[$property]);
7151  } elseif (in_array($property, ['languageField', 'selectFields', 'join', 'leftJoin', 'rightJoin', 'where'], true)) {
7152  $conf[$property] = QueryHelper::quoteDatabaseIdentifiers($connection, $conf[$property]);
7153  }
7154  if (isset($conf[$property . '.'])) {
7155  // stdWrapping already done, so remove the sub-array
7156  unset($conf[$property . '.']);
7157  }
7158  }
7159  // Handle PDO-style named parameter markers first
7160  $queryMarkers = $this->getQueryMarkers($table, $conf);
7161  // Replace the markers in the non-stdWrap properties
7162  foreach ($queryMarkers as $marker => $markerValue) {
7163  $properties = [
7164  'uidInList',
7165  'selectFields',
7166  'where',
7167  'max',
7168  'begin',
7169  'groupBy',
7170  'orderBy',
7171  'join',
7172  'leftjoin',
7173  'rightjoin'
7174  ];
7175  foreach ($properties as $property) {
7176  if ($conf[$property]) {
7177  $conf[$property] = str_replace('###' . $marker . '###', $markerValue, $conf[$property]);
7178  }
7179  }
7180  }
7181 
7182  // Construct WHERE clause:
7183  // Handle recursive function for the pidInList
7184  if (isset($conf['recursive'])) {
7185  $conf['recursive'] = (int)$conf['recursive'];
7186  if ($conf['recursive'] > 0) {
7187  $pidList = GeneralUtility::trimExplode(',', $conf['pidInList'], true);
7188  array_walk($pidList, function (&$storagePid) {
7189  if ($storagePid === 'this') {
7190  $storagePid = $this->getTypoScriptFrontendController()->id;
7191  }
7192  if ($storagePid > 0) {
7193  $storagePid = -$storagePid;
7194  }
7195  });
7196  $expandedPidList = [];
7197  foreach ($pidList as $value) {
7198  // Implementation of getTreeList allows to pass the id negative to include
7199  // it into the result otherwise only childpages are returned
7200  $expandedPidList = array_merge(
7201  GeneralUtility::intExplode(',', $this->getTreeList($value, $conf['recursive'])),
7202  $expandedPidList
7203  );
7204  }
7205  $conf['pidInList'] = implode(',', $expandedPidList);
7206  }
7207  }
7208  if ((string)$conf['pidInList'] === '') {
7209  $conf['pidInList'] = 'this';
7210  }
7211 
7212  $queryParts = $this->getQueryConstraints($table, $conf);
7213 
7214  $queryBuilder = $connection->createQueryBuilder();
7215  // @todo Check against getQueryConstraints, can probably use FrontendRestrictions
7216  // @todo here and remove enableFields there.
7217  $queryBuilder->getRestrictions()->removeAll();
7218  $queryBuilder->select('*')->from($table);
7219 
7220  if ($queryParts['where']) {
7221  $queryBuilder->where($queryParts['where']);
7222  }
7223 
7224  if ($queryParts['groupBy']) {
7225  $queryBuilder->groupBy(...$queryParts['groupBy']);
7226  }
7227 
7228  if (is_array($queryParts['orderBy'])) {
7229  foreach ($queryParts['orderBy'] as $orderBy) {
7230  $queryBuilder->addOrderBy(...$orderBy);
7231  }
7232  }
7233 
7234  // Fields:
7235  if ($conf['selectFields']) {
7236  $queryBuilder->selectLiteral($this->sanitizeSelectPart($conf['selectFields'], $table));
7237  }
7238 
7239  // Setting LIMIT:
7240  $error = false;
7241  if ($conf['max'] || $conf['begin']) {
7242  // Finding the total number of records, if used:
7243  if (strpos(strtolower($conf['begin'] . $conf['max']), 'total') !== false) {
7244  $countQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
7245  $countQueryBuilder->getRestrictions()->removeAll();
7246  $countQueryBuilder->count('*')
7247  ->from($table)
7248  ->where($queryParts['where']);
7249 
7250  if ($queryParts['groupBy']) {
7251  $countQueryBuilder->groupBy(...$queryParts['groupBy']);
7252  }
7253 
7254  try {
7255  $count = $countQueryBuilder->execute()->fetchColumn(0);
7256  $conf['max'] = str_ireplace('total', $count, $conf['max']);
7257  $conf['begin'] = str_ireplace('total', $count, $conf['begin']);
7258  } catch (DBALException $e) {
7259  $this->getTimeTracker()->setTSlogMessage($e->getPrevious()->getMessage());
7260  $error = true;
7261  }
7262  }
7263 
7264  if (!$error) {
7265  $conf['begin'] = MathUtility::forceIntegerInRange(ceil($this->calc($conf['begin'])), 0);
7266  $conf['max'] = MathUtility::forceIntegerInRange(ceil($this->calc($conf['max'])), 0);
7267  if ($conf['begin'] > 0) {
7268  $queryBuilder->setFirstResult($conf['begin']);
7269  }
7270  $queryBuilder->setMaxResults($conf['max'] ?: 100000);
7271  }
7272  }
7273 
7274  if (!$error) {
7275  // Setting up tablejoins:
7276  if ($conf['join']) {
7277  $joinParts = QueryHelper::parseJoin($conf['join']);
7278  $queryBuilder->join(
7279  $table,
7280  $joinParts['tableName'],
7281  $joinParts['tableAlias'],
7282  $joinParts['joinCondition']
7283  );
7284  } elseif ($conf['leftjoin']) {
7285  $joinParts = QueryHelper::parseJoin($conf['leftjoin']);
7286  $queryBuilder->leftJoin(
7287  $table,
7288  $joinParts['tableName'],
7289  $joinParts['tableAlias'],
7290  $joinParts['joinCondition']
7291  );
7292  } elseif ($conf['rightjoin']) {
7293  $joinParts = QueryHelper::parseJoin($conf['rightjoin']);
7294  $queryBuilder->rightJoin(
7295  $table,
7296  $joinParts['tableName'],
7297  $joinParts['tableAlias'],
7298  $joinParts['joinCondition']
7299  );
7300  }
7301 
7302  // Convert the QueryBuilder object into a SQL statement.
7303  $query = $queryBuilder->getSQL();
7304 
7305  // Replace the markers in the queryParts to handle stdWrap enabled properties
7306  foreach ($queryMarkers as $marker => $markerValue) {
7307  // @todo Ugly hack that needs to be cleaned up, with the current architecture
7308  // @todo for exec_Query / getQuery it's the best we can do.
7309  $query = str_replace('###' . $marker . '###', $markerValue, $query);
7310  foreach ($queryParts as $queryPartKey => &$queryPartValue) {
7311  $queryPartValue = str_replace('###' . $marker . '###', $markerValue, $queryPartValue);
7312  }
7313  unset($queryPartValue);
7314  }
7315 
7316  return $returnQueryArray ? $this->getQueryArray($queryBuilder) : $query;
7317  }
7318 
7319  return '';
7320  }
7321 
7330  protected function getQueryArray(QueryBuilder $queryBuilder)
7331  {
7332  $fromClauses = [];
7333  $knownAliases = [];
7334  $queryParts = [];
7335 
7336  // Loop through all FROM clauses
7337  foreach ($queryBuilder->getQueryPart('from') as $from) {
7338  if ($from['alias'] === null) {
7339  $tableSql = $from['table'];
7340  $tableReference = $from['table'];
7341  } else {
7342  $tableSql = $from['table'] . ' ' . $from['alias'];
7343  $tableReference = $from['alias'];
7344  }
7345 
7346  $knownAliases[$tableReference] = true;
7347 
7348  $fromClauses[$tableReference] = $tableSql . $this->getQueryArrayJoinHelper(
7349  $tableReference,
7350  $queryBuilder->getQueryPart('join'),
7351  $knownAliases
7352  );
7353  }
7354 
7355  $queryParts['SELECT'] = implode(', ', $queryBuilder->getQueryPart('select'));
7356  $queryParts['FROM'] = implode(', ', $fromClauses);
7357  $queryParts['WHERE'] = (string)$queryBuilder->getQueryPart('where') ?: '';
7358  $queryParts['GROUPBY'] = implode(', ', $queryBuilder->getQueryPart('groupBy'));
7359  $queryParts['ORDERBY'] = implode(', ', $queryBuilder->getQueryPart('orderBy'));
7360  if ($queryBuilder->getFirstResult() > 0) {
7361  $queryParts['LIMIT'] = $queryBuilder->getFirstResult() . ',' . $queryBuilder->getMaxResults();
7362  } elseif ($queryBuilder->getMaxResults() > 0) {
7363  $queryParts['LIMIT'] = $queryBuilder->getMaxResults();
7364  }
7365 
7366  return $queryParts;
7367  }
7368 
7378  protected function getQueryArrayJoinHelper(string $fromAlias, array $joinParts, array &$knownAliases): string
7379  {
7380  $sql = '';
7381 
7382  if (isset($joinParts['join'][$fromAlias])) {
7383  foreach ($joinParts['join'][$fromAlias] as $join) {
7384  if (array_key_exists($join['joinAlias'], $knownAliases)) {
7385  throw new \RuntimeException(
7386  'Non unique join alias: "' . $join['joinAlias'] . '" found.',
7387  1472748872
7388  );
7389  }
7390  $sql .= ' ' . strtoupper($join['joinType'])
7391  . ' JOIN ' . $join['joinTable'] . ' ' . $join['joinAlias']
7392  . ' ON ' . ((string)$join['joinCondition']);
7393  $knownAliases[$join['joinAlias']] = true;
7394  }
7395 
7396  foreach ($joinParts['join'][$fromAlias] as $join) {
7397  $sql .= $this->getQueryArrayJoinHelper($join['joinAlias'], $joinParts, $knownAliases);
7398  }
7399  }
7400 
7401  return $sql;
7402  }
7412  protected function getQueryConstraints(string $table, array $conf): array
7413  {
7414  // Init:
7415  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
7416  $expressionBuilder = $queryBuilder->expr();
7417  $tsfe = $this->getTypoScriptFrontendController();
7418  $constraints = [];
7419  $pid_uid_flag = 0;
7420  $enableFieldsIgnore = [];
7421  $queryParts = [
7422  'where' => null,
7423  'groupBy' => null,
7424  'orderBy' => null,
7425  ];
7426 
7427  $considerMovePlaceholders = (
7428  $tsfe->sys_page->versioningPreview && $table !== 'pages'
7429  && !empty($GLOBALS['TCA'][$table]['ctrl']['versioningWS'])
7430  );
7431 
7432  if (trim($conf['uidInList'])) {
7433  $listArr = GeneralUtility::intExplode(',', str_replace('this', $tsfe->contentPid, $conf['uidInList']));
7434 
7435  // If move placeholder shall be considered, select via t3ver_move_id
7436  if ($considerMovePlaceholders) {
7437  $constraints[] = (string)$expressionBuilder->orX(
7438  $expressionBuilder->in($table . '.uid', $listArr),
7439  $expressionBuilder->andX(
7440  $expressionBuilder->eq(
7441  $table . '.t3ver_state',
7443  ),
7444  $expressionBuilder->in($table . '.t3ver_move_id', $listArr)
7445  )
7446  );
7447  } else {
7448  $constraints[] = (string)$expressionBuilder->in($table . '.uid', $listArr);
7449  }
7450  $pid_uid_flag++;
7451  }
7452 
7453  // Static_* tables are allowed to be fetched from root page
7454  if (strpos($table, 'static_') === 0) {
7455  $pid_uid_flag++;
7456  }
7457 
7458  if (trim($conf['pidInList'])) {
7459  $listArr = GeneralUtility::intExplode(',', str_replace('this', $tsfe->contentPid, $conf['pidInList']));
7460  // Removes all pages which are not visible for the user!
7461  $listArr = $this->checkPidArray($listArr);
7462  if (GeneralUtility::inList($conf['pidInList'], 'root')) {
7463  $listArr[] = 0;
7464  }
7465  if (GeneralUtility::inList($conf['pidInList'], '-1')) {
7466  $listArr[] = -1;
7467  $enableFieldsIgnore['pid'] = true;
7468  }
7469  if (!empty($listArr)) {
7470  $constraints[] = $expressionBuilder->in($table . '.pid', array_map('intval', $listArr));
7471  $pid_uid_flag++;
7472  } else {
7473  // If not uid and not pid then uid is set to 0 - which results in nothing!!
7474  $pid_uid_flag = 0;
7475  }
7476  }
7477 
7478  // If not uid and not pid then uid is set to 0 - which results in nothing!!
7479  if (!$pid_uid_flag) {
7480  $constraints[] = $expressionBuilder->eq($table . '.uid', 0);
7481  }
7482 
7483  $where = isset($conf['where.']) ? trim($this->stdWrap($conf['where'], $conf['where.'])) : trim($conf['where']);
7484  if ($where) {
7485  $constraints[] = QueryHelper::stripLogicalOperatorPrefix($where);
7486  }
7487 
7488  // Check if the table is translatable, and set the language field by default from the TCA information
7489  $languageField = '';
7490  if (!empty($conf['languageField']) || !isset($conf['languageField'])) {
7491  if (isset($conf['languageField']) && !empty($GLOBALS['TCA'][$table]['columns'][$conf['languageField']])) {
7492  $languageField = $conf['languageField'];
7493  } elseif (!empty($GLOBALS['TCA'][$table]['ctrl']['languageField']) && !empty($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])) {
7494  $languageField = $table . '.' . $GLOBALS['TCA'][$table]['ctrl']['languageField'];
7495  }
7496  }
7497 
7498  if (!empty($languageField)) {
7499  // The sys_language record UID of the content of the page
7500  $sys_language_content = (int)$tsfe->sys_language_content;
7501 
7502  if ($tsfe->sys_language_contentOL && !empty($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])) {
7503  // Sys language content is set to zero/-1 - and it is expected that whatever routine processes the output will
7504  // OVERLAY the records with localized versions!
7505  $languageQuery = $expressionBuilder->in($languageField, [0, -1]);
7506  // Use this option to include records that don't have a default translation
7507  // (originalpointerfield is 0 and the language field contains the requested language)
7508  $includeRecordsWithoutDefaultTranslation = isset($conf['includeRecordsWithoutDefaultTranslation.']) ?
7509  $this->stdWrap($conf['includeRecordsWithoutDefaultTranslation'], $conf['includeRecordsWithoutDefaultTranslation.']) :
7510  $conf['includeRecordsWithoutDefaultTranslation'];
7511  if (trim($includeRecordsWithoutDefaultTranslation) !== '') {
7512  $languageQuery = $expressionBuilder->orX(
7513  $languageQuery,
7514  $expressionBuilder->andX(
7515  $expressionBuilder->eq($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], 0),
7516  $expressionBuilder->eq($languageField, $sys_language_content)
7517  )
7518  );
7519  }
7520  } else {
7521  $languageQuery = $expressionBuilder->eq($languageField, $sys_language_content);
7522  }
7523  $constraints[] = $languageQuery;
7524  }
7525 
7526  // Enablefields
7527  if ($table === 'pages') {
7528  $constraints[] = QueryHelper::stripLogicalOperatorPrefix($tsfe->sys_page->where_hid_del);
7529  $constraints[] = QueryHelper::stripLogicalOperatorPrefix($tsfe->sys_page->where_groupAccess);
7530  } else {
7531  $constraints[] = QueryHelper::stripLogicalOperatorPrefix($this->enableFields($table, false, $enableFieldsIgnore));
7532  }
7533 
7534  // MAKE WHERE:
7535  if (count($constraints) !== 0) {
7536  $queryParts['where'] = $expressionBuilder->andX(...$constraints);
7537  }
7538  // GROUP BY
7539  if (trim($conf['groupBy'])) {
7540  $groupBy = isset($conf['groupBy.'])
7541  ? trim($this->stdWrap($conf['groupBy'], $conf['groupBy.']))
7542  : trim($conf['groupBy']);
7543  $queryParts['groupBy'] = QueryHelper::parseGroupBy($groupBy);
7544  }
7545 
7546  // ORDER BY
7547  if (trim($conf['orderBy'])) {
7548  $orderByString = isset($conf['orderBy.'])
7549  ? trim($this->stdWrap($conf['orderBy'], $conf['orderBy.']))
7550  : trim($conf['orderBy']);
7551 
7552  $queryParts['orderBy'] = QueryHelper::parseOrderBy($orderByString);
7553  }
7554 
7555  // Return result:
7556  return $queryParts;
7557  }
7558 
7570  public function getWhere($table, $conf, $returnQueryArray = false)
7571  {
7573  // Init:
7574  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
7575  $queryConstraints = $this->getQueryConstraints($table, $conf);
7576  $query = '';
7577 
7578  $queryParts = [
7579  'SELECT' => '',
7580  'FROM' => '',
7581  'WHERE' => '',
7582  'GROUPBY' => '',
7583  'ORDERBY' => '',
7584  'LIMIT' => ''
7585  ];
7586 
7587  // MAKE WHERE:
7588  if (!empty($queryConstraints['where'])) {
7589  $queryParts['WHERE'] = (string)$queryConstraints['where'];
7590  $query = 'WHERE ' . $queryParts['WHERE'];
7591  }
7592 
7593  // GROUP BY
7594  if (!empty($queryConstraints['groupBy'])) {
7595  $queryParts['GROUPBY'] = implode(
7596  ', ',
7597  array_map([$queryBuilder, 'quoteIdentifier'], $queryConstraints['groupBy'])
7598  );
7599  $query .= ' GROUP BY ' . $queryParts['GROUPBY'];
7600  }
7601 
7602  // ORDER BY
7603  if (!empty($queryConstraints['orderBy'])) {
7604  $orderBy = [];
7605  foreach ($queryConstraints['orderBy'] as $orderPair) {
7606  list($fieldName, $direction) = $orderPair;
7607  $orderBy[] = trim($queryBuilder->quoteIdentifier($fieldName) . ' ' . $direction);
7608  }
7609  $queryParts['ORDERBY'] = implode(', ', $orderBy);
7610  $query .= ' ORDER BY ' . $queryParts['ORDERBY'];
7611  }
7612 
7613  // Return result:
7614  return $returnQueryArray ? $queryParts : $query;
7615  }
7616 
7629  protected function sanitizeSelectPart($selectPart, $table)
7630  {
7631  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
7632 
7633  // Pattern matching parts
7634  $matchStart = '/(^\\s*|,\\s*|' . $table . '\\.)';
7635  $matchEnd = '(\\s*,|\\s*$)/';
7636  $necessaryFields = ['uid', 'pid'];
7637  $wsFields = ['t3ver_state'];
7638  if (isset($GLOBALS['TCA'][$table]) && !preg_match(($matchStart . '\\*' . $matchEnd), $selectPart) && !preg_match('/(count|max|min|avg|sum)\\([^\\)]+\\)/i', $selectPart)) {
7639  foreach ($necessaryFields as $field) {
7640  $match = $matchStart . $field . $matchEnd;
7641  if (!preg_match($match, $selectPart)) {
7642  $selectPart .= ', ' . $connection->quoteIdentifier($table . '.' . $field) . ' AS ' . $connection->quoteIdentifier($field);
7643  }
7644  }
7645  if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
7646  foreach ($wsFields as $field) {
7647  $match = $matchStart . $field . $matchEnd;
7648  if (!preg_match($match, $selectPart)) {
7649  $selectPart .= ', ' . $connection->quoteIdentifier($table . '.' . $field) . ' AS ' . $connection->quoteIdentifier($field);
7650  }
7651  }
7652  }
7653  }
7654  return $selectPart;
7655  }
7656 
7665  public function checkPidArray($listArr)
7666  {
7667  if (!is_array($listArr) || empty($listArr)) {
7668  return [];
7669  }
7670  $outArr = [];
7671  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
7672  $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
7673  $queryBuilder->select('uid')
7674  ->from('pages')
7675  ->where(
7676  $queryBuilder->expr()->in(
7677  'uid',
7678  $queryBuilder->createNamedParameter($listArr, Connection::PARAM_INT_ARRAY)
7679  ),
7680  $queryBuilder->expr()->notIn(
7681  'doktype',
7682  $queryBuilder->createNamedParameter(
7683  GeneralUtility::intExplode(',', $this->checkPid_badDoktypeList, true),
7684  Connection::PARAM_INT_ARRAY
7685  )
7686  )
7687  );
7688  try {
7689  $result = $queryBuilder->execute();
7690  while ($row = $result->fetch()) {
7691  $outArr[] = $row['uid'];
7692  }
7693  } catch (DBALException $e) {
7694  $this->getTimeTracker()->setTSlogMessage($e->getMessage() . ': ' . $queryBuilder->getSQL(), 3);
7695  }
7696 
7697  return $outArr;
7698  }
7699 
7708  public function checkPid($uid)
7709  {
7710  $uid = (int)$uid;
7711  if (!isset($this->checkPid_cache[$uid])) {
7712  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
7713  $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
7714  $count = $queryBuilder->count('*')
7715  ->from('pages')
7716  ->where(
7717  $queryBuilder->expr()->eq(
7718  'uid',
7719  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
7720  ),
7721  $queryBuilder->expr()->notIn(
7722  'doktype',
7723  $queryBuilder->createNamedParameter(
7724  GeneralUtility::intExplode(',', $this->checkPid_badDoktypeList, true),
7725  Connection::PARAM_INT_ARRAY
7726  )
7727  )
7728  )
7729  ->execute()
7730  ->fetchColumn(0);
7731 
7732  $this->checkPid_cache[$uid] = (bool)$count;
7733  }
7734  return $this->checkPid_cache[$uid];
7735  }
7736 
7747  public function getQueryMarkers($table, $conf)
7748  {
7749  if (!is_array($conf['markers.'])) {
7750  return [];
7751  }
7752  // Parse markers and prepare their values
7753  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
7754  $markerValues = [];
7755  foreach ($conf['markers.'] as $dottedMarker => $dummy) {
7756  $marker = rtrim($dottedMarker, '.');
7757  if ($dottedMarker != $marker . '.') {
7758  continue;
7759  }
7760  // Parse definition
7761  $tempValue = isset($conf['markers.'][$dottedMarker])
7762  ? $this->stdWrap($conf['markers.'][$dottedMarker]['value'], $conf['markers.'][$dottedMarker])
7763  : $conf['markers.'][$dottedMarker]['value'];
7764  // Quote/escape if needed
7765  if (is_numeric($tempValue)) {
7766  if ((int)$tempValue == $tempValue) {
7767  // Handle integer
7768  $markerValues[$marker] = (int)$tempValue;
7769  } else {
7770  // Handle float
7771  $markerValues[$marker] = (float)$tempValue;
7772  }
7773  } elseif (is_null($tempValue)) {
7774  // It represents NULL
7775  $markerValues[$marker] = 'NULL';
7776  } elseif (!empty($conf['markers.'][$dottedMarker]['commaSeparatedList'])) {
7777  // See if it is really a comma separated list of values
7778  $explodeValues = GeneralUtility::trimExplode(',', $tempValue);
7779  if (count($explodeValues) > 1) {
7780  // Handle each element of list separately
7781  $tempArray = [];
7782  foreach ($explodeValues as $listValue) {
7783  if (is_numeric($listValue)) {
7784  if ((int)$listValue == $listValue) {
7785  $tempArray[] = (int)$listValue;
7786  } else {
7787  $tempArray[] = (float)$listValue;
7788  }
7789  } else {
7790  // If quoted, remove quotes before
7791  // escaping.
7792  if (preg_match('/^\'([^\']*)\'$/', $listValue, $matches)) {
7793  $listValue = $matches[1];
7794  } elseif (preg_match('/^\\"([^\\"]*)\\"$/', $listValue, $matches)) {
7795  $listValue = $matches[1];
7796  }
7797  $tempArray[] = $connection->quote($listValue);
7798  }
7799  }
7800  $markerValues[$marker] = implode(',', $tempArray);
7801  } else {
7802  // Handle remaining values as string
7803  $markerValues[$marker] = $connection->quote($tempValue);
7804  }
7805  } else {
7806  // Handle remaining values as string
7807  $markerValues[$marker] = $connection->quote($tempValue);
7808  }
7809  }
7810  return $markerValues;
7811  }
7812 
7813  /***********************************************
7814  *
7815  * Frontend editing functions
7816  *
7817  ***********************************************/
7829  public function editPanel($content, $conf, $currentRecord = '', $dataArr = [])
7830  {
7831  if ($this->getTypoScriptFrontendController()->beUserLogin && $this->getFrontendBackendUser()->frontendEdit instanceof FrontendEditingController) {
7832  if (!$currentRecord) {
7834  }
7835  if (empty($dataArr)) {
7836  $dataArr = $this->data;
7837  }
7838  // Delegate rendering of the edit panel to the frontend edit
7839  $content = $this->getFrontendBackendUser()->frontendEdit->displayEditPanel($content, $conf, $currentRecord, $dataArr);
7840  }
7841  return $content;
7842  }
7843 
7856  public function editIcons($content, $params, array $conf = [], $currentRecord = '', $dataArr = [], $addUrlParamStr = '')
7857  {
7858  if ($this->getTypoScriptFrontendController()->beUserLogin && $this->getFrontendBackendUser()->frontendEdit instanceof FrontendEditingController) {
7859  if (!$currentRecord) {
7861  }
7862  if (empty($dataArr)) {
7863  $dataArr = $this->data;
7864  }
7865  // Delegate rendering of the edit panel to frontend edit class.
7866  $content = $this->getFrontendBackendUser()->frontendEdit->displayEditIcons($content, $params, $conf, $currentRecord, $dataArr, $addUrlParamStr);
7867  }
7868  return $content;
7869  }
7870 
7880  public function isDisabled($table, $row)
7881  {
7882  $tsfe = $this->getTypoScriptFrontendController();
7883  $enablecolumns = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns'];
7884  return $enablecolumns['disabled'] && $row[$enablecolumns['disabled']]
7885  || $enablecolumns['fe_group'] && $tsfe->simUserGroup && (int)$row[$enablecolumns['fe_group']] === (int)$tsfe->simUserGroup
7886  || $enablecolumns['starttime'] && $row[$enablecolumns['starttime']] > $GLOBALS['EXEC_TIME']
7887  || $enablecolumns['endtime'] && $row[$enablecolumns['endtime']] && $row[$enablecolumns['endtime']] < $GLOBALS['EXEC_TIME'];
7888  }
7889 
7895  protected function getResourceFactory()
7896  {
7898  }
7899 
7907  protected function getEnvironmentVariable($key)
7908  {
7909  return GeneralUtility::getIndpEnv($key);
7910  }
7911 
7919  protected function getFromCache(array $configuration)
7920  {
7921  $content = false;
7922 
7923  $cacheKey = $this->calculateCacheKey($configuration);
7924  if (!empty($cacheKey)) {
7926  $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)
7927  ->getCache('cache_hash');
7928  $content = $cacheFrontend->get($cacheKey);
7929  }
7930  return $content;
7931  }
7932 
7939  protected function calculateCacheLifetime(array $configuration)
7940  {
7941  $lifetimeConfiguration = isset($configuration['lifetime'])
7942  ? $configuration['lifetime']
7943  : '';
7944  $lifetimeConfiguration = isset($configuration['lifetime.'])
7945  ? $this->stdWrap($lifetimeConfiguration, $configuration['lifetime.'])
7946  : $lifetimeConfiguration;
7947 
7948  $lifetime = null; // default lifetime
7949  if (strtolower($lifetimeConfiguration) === 'unlimited') {
7950  $lifetime = 0; // unlimited
7951  } elseif ($lifetimeConfiguration > 0) {
7952  $lifetime = (int)$lifetimeConfiguration; // lifetime in seconds
7953  }
7954  return $lifetime;
7955  }
7956 
7963  protected function calculateCacheTags(array $configuration)
7964  {
7965  $tags = isset($configuration['tags']) ? $configuration['tags'] : '';
7966  $tags = isset($configuration['tags.'])
7967  ? $this->stdWrap($tags, $configuration['tags.'])
7968  : $tags;
7969  return empty($tags) ? [] : GeneralUtility::trimExplode(',', $tags);
7970  }
7971 
7978  protected function calculateCacheKey(array $configuration)
7979  {
7980  $key = isset($configuration['key']) ? $configuration['key'] : '';
7981  return isset($configuration['key.'])
7982  ? $this->stdWrap($key, $configuration['key.'])
7983  : $key;
7984  }
7985 
7991  protected function getFrontendBackendUser()
7992  {
7993  return $GLOBALS['BE_USER'];
7994  }
7995 
7999  protected function getTimeTracker()
8000  {
8001  return GeneralUtility::makeInstance(TimeTracker::class);
8002  }
8003 
8007  protected function getTypoScriptFrontendController()
8008  {
8009  return $this->typoScriptFrontendController ?: $GLOBALS['TSFE'];
8010  }
8011 }
static intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
static implodeAttributes(array $arr, $xhtmlSafe=false, $dontOmitBlankAttribs=false)
resolveMixedLinkParameter($linkText, $mixedLinkParameter, &$configuration=[])
editIcons($content, $params, array $conf=[], $currentRecord='', $dataArr=[], $addUrlParamStr='')
debug($variable='', $name=' *variable *', $line=' *line *', $file=' *file *', $recursiveDepth=3, $debugLevel='E_DEBUG')
static isFirstPartOfStr($str, $partStr)
createCropAreaFromJsonString(string $cropSettings, string $cropVariant)
static forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
Definition: MathUtility.php:31
static callUserFunction($funcName, &$params, &$ref, $_='', $errorMode=0)
rootLineValue($key, $field, $slideBack=false, $altRootLine='')
static hmac($input, $additionalSecret='')
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static calculateWithParentheses($string)
searchWhere($searchWords, $searchFieldList, $searchTable='')
static makeInstance($className,... $constructorArguments)
static split_fileref($fileNameWithPath)
static create(string $jsonString, array $tcaConfig=[])
static implodeArrayForUrl($name, array $theArray, $str='', $skipBlank=false, $rawurlencodeParamName=false)
enableFields($table, $show_hidden=false, array $ignore_array=[])
editPanel($content, $conf, $currentRecord='', $dataArr=[])
getQueryArguments($conf, $overruleQueryArguments=[], $forceOverruleArguments=false)
static explodeUrl2Array($string, $multidim=false)
static filterAndSortByNumericKeys($setupArr, $acceptAnyKeys=false)
static splitCalc($string, $operators)
getQueryArrayJoinHelper(string $fromAlias, array $joinParts, array &$knownAliases)
static mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys=true, $includeEmptyValues=true, $enableUnsetFeature=true)
__construct(TypoScriptFrontendController $typoScriptFrontendController=null)
static xml2array($string, $NSprefix='', $reportDocTag=false)
static stripLogicalOperatorPrefix(string $constraint)
getTypoLink($label, $params, $urlParameters=[], $target='')
static formatSize($sizeInBytes, $labels='', $base=0)
static revExplode($delimiter, $string, $count=0)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static quoteDatabaseIdentifiers(Connection $connection, string $sql)
getTypoLink_URL($params, $urlParameters=[], $target='')
static uniqueList($in_list, $secondParameter=null)
static arrayDiffAssocRecursive(array $array1, array $array2)
static endsWith($haystack, $needle)