‪TYPO3CMS  11.5
ExtendedTemplateService.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
19 use TYPO3\CMS\Backend\Utility\BackendUtility;
23 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
37 
44 class ExtendedTemplateService extends ‪TemplateService
45 {
49  protected $categories = [
50  'basic' => [],
51  // Constants of superior importance for the template-layout. This is dimensions, imagefiles and enabling of various features. The most basic constants, which you would almost always want to configure.
52  'menu' => [],
53  // Menu setup. This includes fontfiles, sizes, background images. Depending on the menutype.
54  'content' => [],
55  // All constants related to the display of pagecontent elements
56  'page' => [],
57  // General configuration like metatags, link targets
58  'advanced' => [],
59  // Advanced functions, which are used very seldom.
60  'all' => [],
61  ];
62 
68  public $ext_inBrace = 0;
69 
75  public $tsbrowser_searchKeys = [];
76 
80  public $tsbrowser_depthKeys = [];
81 
85  public $constantMode = '';
86 
90  public $regexMode = '';
91 
95  public $ext_expandAllNotes = 0;
96 
100  public $ext_noPMicons = 0;
101 
107  public $templateTitles = [];
108 
112  protected $lnToScript;
113 
117  public $clearList_const_temp;
118 
122  public $clearList_setup_temp;
123 
127  public $bType = '';
128 
132  public $linkObjects = false;
133 
137  public $changed = false;
138 
142  protected $objReg = [];
143 
147  public $raw = [];
148 
152  public $rawP = 0;
153 
157  public $lastComment = '';
158 
162  protected $javaScriptInstructions = [];
163 
167  private $constantParser;
168 
173  public function __construct(?‪Context $context = null, ?‪ConstantConfigurationParser $constantParser = null)
174  {
175  parent::__construct($context);
176  $this->constantParser = $constantParser ?? GeneralUtility::makeInstance(ConstantConfigurationParser::class);
177  // Disabled in backend context
178  $this->tt_track = false;
179  $this->verbose = false;
180  }
181 
185  public function getJavaScriptInstructions(): array
186  {
187  return $this->javaScriptInstructions;
188  }
189 
196  public function substituteConstants($all)
197  {
198  return preg_replace_callback('/\\{\\$(.[^}]+)\\}/', [$this, 'substituteConstantsCallBack'], $all);
199  }
200 
208  public function substituteConstantsCallBack($matches)
209  {
210  $marker = substr(md5($matches[0]), 0, 6);
211  switch ($this->constantMode) {
212  case 'const':
213  $ret_val = isset($this->flatSetup[$matches[1]]) && !is_array($this->flatSetup[$matches[1]]) ? '##' . $marker . '_B##' . $this->flatSetup[$matches[1]] . '##' . $marker . '_M##' . $matches[0] . '##' . $marker . '_E##' : $matches[0];
214  break;
215  case 'subst':
216  $ret_val = isset($this->flatSetup[$matches[1]]) && !is_array($this->flatSetup[$matches[1]]) ? '##' . $marker . '_B##' . $matches[0] . '##' . $marker . '_M##' . $this->flatSetup[$matches[1]] . '##' . $marker . '_E##' : $matches[0];
217  break;
218  case 'untouched':
219  $ret_val = $matches[0];
220  break;
221  default:
222  $ret_val = isset($this->flatSetup[$matches[1]]) && !is_array($this->flatSetup[$matches[1]]) ? $this->flatSetup[$matches[1]] : $matches[0];
223  }
224  return $ret_val;
225  }
226 
234  public function substituteCMarkers($all)
235  {
236  switch ($this->constantMode) {
237  case 'const':
238  case 'subst':
239  $all = preg_replace(
240  '/##[a-z0-9]{6}_B##(.*?)##[a-z0-9]{6}_M##(.*?)##[a-z0-9]{6}_E##/',
241  '<strong class="text-success" data-bs-toggle="tooltip" data-bs-placement="top" data-title="$1" title="$1">$2</strong>',
242  $all
243  );
244  break;
245  default:
246  }
247  return $all;
248  }
249 
256  public function generateConfig_constants()
257  {
258  // Parse constants
259  $constants = GeneralUtility::makeInstance(TypoScriptParser::class);
260  // Register comments!
261  $constants->regComments = true;
262  $matchObj = GeneralUtility::makeInstance(ConditionMatcher::class);
263  // Matches ALL conditions in TypoScript
264  $matchObj->setSimulateMatchResult(true);
265  $c = 0;
266  $cc = count($this->constants);
267  $defaultConstants = [];
268  foreach ($this->constants as $str) {
269  $c++;
270  if ($c == $cc) {
271  $defaultConstants = ‪ArrayUtility::flatten($constants->setup, '', true);
272  }
273  $constants->parse($str, $matchObj);
274  }
275  $this->setup['constants'] = $constants->setup;
276  $flatSetup = ‪ArrayUtility::flatten($constants->setup, '', true);
277  return $this->constantParser->parseComments(
278  $flatSetup,
279  $defaultConstants
280  );
281  }
282 
288  public function ext_getSetup($theSetup, $theKey)
289  {
290  $theKey = trim((string)$theKey);
291  if (empty($theKey)) {
292  // Early return the whole setup in case key is empty
293  return [(array)$theSetup, ''];
294  }
295  // 'a.b.c' --> ['a', 'b.c']
296  $parts = explode('.', $theKey, 2);
297  $pathSegment = $parts[0] ?? '';
298  $pathRest = trim($parts[1] ?? '');
299  if ($pathSegment !== '' && is_array($theSetup[$pathSegment . '.'] ?? false)) {
300  if ($pathRest !== '') {
301  // Current path segment is a sub array, check it recursively by applying the rest of the key
302  return $this->ext_getSetup($theSetup[$pathSegment . '.'], $pathRest);
303  }
304  // No further path to evaluate, return current setup and the value for the current path segment - if any
305  return [$theSetup[$pathSegment . '.'], $theSetup[$pathSegment] ?? ''];
306  }
307  // Return the key value - if any - along with an empty setup since no sub array exists
308  return [[], $theSetup[$theKey] ?? ''];
309  }
310 
322  public function ext_getObjTree($arr, $depth_in, $depthData, $parentType = '', $parentValue = '', $alphaSort = '0')
323  {
324  $HTML = '';
325  if ($alphaSort == '1') {
326  ksort($arr);
327  }
328  $keyArr_num = [];
329  $keyArr_alpha = [];
330  $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
331  foreach ($arr as $key => $value) {
332  // Don't do anything with comments / linenumber registrations...
333  if (substr($key, -2) !== '..') {
334  $key = preg_replace('/\\.$/', '', $key) ?? '';
335  if (substr($key, -1) !== '.') {
337  $keyArr_num[$key] = $arr[$key] ?? '';
338  } else {
339  $keyArr_alpha[$key] = $arr[$key] ?? '';
340  }
341  }
342  }
343  }
344  ksort($keyArr_num);
345  $keyArr = $keyArr_num + $keyArr_alpha;
346  if ($depth_in) {
347  $depth_in = $depth_in . '.';
348  }
349  foreach ($keyArr as $key => $value) {
350  $depth = $depth_in . $key;
351  // This excludes all constants starting with '_' from being shown.
352  if ($this->bType !== 'const' || $depth[0] !== '_') {
353  $goto = substr(md5($depth), 0, 6);
354  $deeper = is_array($arr[$key . '.'] ?? null) && (($this->tsbrowser_depthKeys[$depth] ?? false) || $this->ext_expandAllNotes);
355  $PM = is_array($arr[$key . '.'] ?? null) && !$this->ext_noPMicons ? ($deeper ? 'minus' : 'plus') : 'join';
356  $HTML .= $depthData . '<li><span class="list-tree-group">';
357  if ($PM !== 'join') {
358  $urlParameters = [
359  'id' => (int)GeneralUtility::_GP('id'),
360  'tsbr[' . $depth . ']' => $deeper ? 0 : 1,
361  ];
362  $aHref = (string)$uriBuilder->buildUriFromRoute('web_ts', $urlParameters) . '#' . $goto;
363  $HTML .= '<a class="list-tree-control' . ($PM === 'minus' ? ' list-tree-control-open' : ' list-tree-control-closed') . '" name="' . $goto . '" href="' . htmlspecialchars($aHref) . '"><i class="fa"></i></a>';
364  }
365  $label = $key;
366  // Read only...
367  if (($depth === 'types') && $this->bType === 'setup') {
368  $label = '<span style="color: #666666;">' . $label . '</span>';
369  } else {
370  if ($this->linkObjects) {
371  $urlParameters = [
372  'id' => (int)GeneralUtility::_GP('id'),
373  'sObj' => $depth,
374  ];
375  $aHref = (string)$uriBuilder->buildUriFromRoute('web_ts', $urlParameters);
376  if ($this->bType !== 'const') {
377  $ln = is_array($arr[$key . '.ln..'] ?? null) ? 'Defined in: ' . $this->lineNumberToScript($arr[$key . '.ln..']) : 'N/A';
378  } else {
379  $ln = '';
380  }
381  if (($this->tsbrowser_searchKeys[$depth] ?? 0) & 4) {
382  // The key has matched the search string
383  $label = '<strong class="text-danger">' . $label . '</strong>';
384  }
385  $label = '<a href="' . htmlspecialchars($aHref) . '" title="' . htmlspecialchars($depth_in . $key . ' ' . $ln) . '">' . $label . '</a>';
386  }
387  }
388  $HTML .= '<span class="list-tree-label" title="' . htmlspecialchars($depth_in . $key) . '">[' . $label . ']</span>';
389  if (isset($arr[$key])) {
390  $theValue = $arr[$key];
391  // The value has matched the search string
392  if (($this->tsbrowser_searchKeys[$depth] ?? 0) & 2) {
393  $HTML .= ' = <span class="list-tree-value text-danger">' . htmlspecialchars($theValue) . '</span>';
394  } else {
395  $HTML .= ' = <span class="list-tree-value">' . htmlspecialchars($theValue) . '</span>';
396  }
397  if ($this->ext_regComments && isset($arr[$key . '..'])) {
398  $comment = (string)$arr[$key . '..'];
399  // Skip INCLUDE_TYPOSCRIPT comments, they are almost useless
400  if (!preg_match('/### <INCLUDE_TYPOSCRIPT:.*/', $comment)) {
401  // Remove linebreaks, replace with ' '
402  $comment = preg_replace('/[\\r\\n]/', ' ', $comment) ?? '';
403  // Remove # and * if more than twice in a row
404  $comment = preg_replace('/[#\\*]{2,}/', '', $comment) ?? '';
405  // Replace leading # (just if it exists) and add it again. Result: Every comment should be prefixed by a '#'.
406  $comment = preg_replace('/^[#\\*\\s]+/', '# ', $comment) ?? '';
407  // Masking HTML Tags: Replace < with &lt; and > with &gt;
408  $comment = htmlspecialchars($comment);
409  $HTML .= ' <i class="text-muted">' . trim($comment) . '</i>';
410  }
411  }
412  }
413  $HTML .= '</span>';
414  if ($deeper) {
415  $HTML .= $this->ext_getObjTree($arr[$key . '.'] ?? [], $depth, $depthData, '', $arr[$key] ?? '', $alphaSort);
416  }
417  }
418  }
419  if ($HTML !== '') {
420  $HTML = '<ul class="list-tree text-monospace">' . $HTML . '</ul>';
421  }
422 
423  return $HTML;
424  }
425 
436  public function lineNumberToScript(array $lnArr)
437  {
438  // On the first call, construct the lnToScript array.
439  if (!is_array($this->lnToScript)) {
440  $this->lnToScript = [];
441 
442  // aggregatedTotalLineCount
443  $c = 0;
444  foreach ($this->hierarchyInfo as $templateNumber => $info) {
445  // hierarchyInfo has the number of lines in configLines, but unfortunately this value
446  // was calculated *before* processing of any INCLUDE instructions
447  // for some yet unknown reason we have to add an extra +2 offset
448  $linecountAfterIncludeProcessing = substr_count($this->config[$templateNumber], LF) + 2;
449  $c += $linecountAfterIncludeProcessing;
450  $this->lnToScript[$c] = $info['title'];
451  }
452  }
453 
454  foreach ($lnArr as $k => $ln) {
455  foreach ($this->lnToScript as $endLn => $title) {
456  if ($endLn >= (int)$ln) {
457  $lnArr[$k] = '"' . $title . '", ' . $ln;
458  break;
459  }
460  }
461  }
462 
463  return implode('; ', $lnArr);
464  }
465 
474  public function ext_getSearchKeys($arr, $depth_in, $searchString, $keyArray)
475  {
476  $keyArr = [];
477  foreach ($arr as $key => $value) {
478  $key = preg_replace('/\\.$/', '', $key) ?? '';
479  if (substr($key, -1) !== '.') {
480  $keyArr[$key] = 1;
481  }
482  }
483  if ($depth_in) {
484  $depth_in = $depth_in . '.';
485  }
486  $searchPattern = '';
487  if ($this->regexMode) {
488  $searchPattern = '/' . addcslashes($searchString, '/') . '/';
489  $matchResult = @preg_match($searchPattern, '');
490  if ($matchResult === false) {
491  throw new Exception(sprintf('Error evaluating regular expression "%s".', $searchPattern), 1446559458);
492  }
493  }
494  foreach ($keyArr as $key => $value) {
495  $depth = $depth_in . $key;
496  if ($this->regexMode) {
497  // The value has matched
498  if (($arr[$key] ?? false) && preg_match($searchPattern, $arr[$key])) {
499  $this->tsbrowser_searchKeys[$depth] = ($this->tsbrowser_searchKeys[$depth] ?? 0) + 2;
500  }
501  // The key has matched
502  if (preg_match($searchPattern, $key)) {
503  $this->tsbrowser_searchKeys[$depth] = ($this->tsbrowser_searchKeys[$depth] ?? 0) + 4;
504  }
505  // Just open this subtree if the parent key has matched the search
506  if (preg_match($searchPattern, $depth_in)) {
507  $this->tsbrowser_searchKeys[$depth] = 1;
508  }
509  } else {
510  // The value has matched
511  if (($arr[$key] ?? false) && stripos($arr[$key], $searchString) !== false) {
512  $this->tsbrowser_searchKeys[$depth] = ($this->tsbrowser_searchKeys[$depth] ?? 0) + 2;
513  }
514  // The key has matches
515  if (stripos($key, $searchString) !== false) {
516  $this->tsbrowser_searchKeys[$depth] = ($this->tsbrowser_searchKeys[$depth] ?? 0) + 4;
517  }
518  // Just open this subtree if the parent key has matched the search
519  if (stripos($depth_in, $searchString) !== false) {
520  $this->tsbrowser_searchKeys[$depth] = 1;
521  }
522  }
523  if (is_array($arr[$key . '.'] ?? null)) {
524  $cS = count($this->tsbrowser_searchKeys);
525  $keyArray = $this->ext_getSearchKeys($arr[$key . '.'], $depth, $searchString, $keyArray);
526  if ($cS !== count($this->tsbrowser_searchKeys)) {
527  $keyArray[$depth] = 1;
528  }
529  }
530  }
531  return $keyArray;
532  }
533 
538  public function ext_getRootlineNumber($pid)
539  {
540  if ($pid) {
541  foreach ($this->getRootLine() as $key => $val) {
542  if ((int)$val['uid'] === (int)$pid) {
543  return (int)$key;
544  }
545  }
546  }
547  return -1;
548  }
549 
557  public function ext_getTemplateHierarchyArr($arr, $depthData, $keyArray, $first = 0)
558  {
559  $keyArr = [];
560  foreach ($arr as $key => $value) {
561  $key = preg_replace('/\\.$/', '', $key) ?? '';
562  if (substr($key, -1) !== '.') {
563  $keyArr[$key] = 1;
564  }
565  }
566  $a = 0;
567  $c = count($keyArr);
568  $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
569  $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
570  foreach ($keyArr as $key => $value) {
571  $HTML = '';
572  $a++;
573  $deeper = is_array($arr[$key . '.'] ?? false);
574  $row = $arr[$key];
575  $LN = $a == $c ? 'blank' : 'line';
576  $BTM = $a == $c ? 'top' : '';
577  $HTML .= $depthData;
578  $alttext = '[' . $row['templateID'] . ']';
579  $alttext .= $row['pid'] ? ' - ' . BackendUtility::getRecordPath($row['pid'], '1=1', 20) : '';
580  $icon = strpos($row['templateID'], 'sys') === 0
581  ? '<span title="' . htmlspecialchars($alttext) . '">' . $iconFactory->getIconForRecord('sys_template', $row, ‪Icon::SIZE_SMALL)->render() . '</span>'
582  : '<span title="' . htmlspecialchars($alttext) . '">' . $iconFactory->getIcon('mimetypes-x-content-template-static', ‪Icon::SIZE_SMALL)->render() . '</span>';
583  if (in_array($row['templateID'], $this->clearList_const) || in_array($row['templateID'], $this->clearList_setup)) {
584  $urlParameters = [
585  'id' => (int)GeneralUtility::_GP('id'),
586  'template' => $row['templateID'],
587  ];
588  $aHref = (string)$uriBuilder->buildUriFromRoute('web_ts', $urlParameters);
589  $A_B = '<a href="' . htmlspecialchars($aHref) . '">';
590  $A_E = '</a>';
591  if (GeneralUtility::_GP('template') == $row['templateID']) {
592  $A_B = '<strong>' . $A_B;
593  $A_E .= '</strong>';
594  }
595  } else {
596  $A_B = '';
597  $A_E = '';
598  }
599  $HTML .= ($first ? '' : '<span class="treeline-icon treeline-icon-join' . $BTM . '"></span>') . $icon . ' ' . $A_B
600  . htmlspecialchars(GeneralUtility::fixed_lgd_cs($row['title'], ‪$GLOBALS['BE_USER']->uc['titleLen']))
601  . $A_E . '&nbsp;&nbsp;';
602  $RL = $this->ext_getRootlineNumber($row['pid']);
603  $statusCheckedIcon = $iconFactory->getIcon('status-status-checked', ‪Icon::SIZE_SMALL)->render();
604  $keyArray[] = '<tr>
605  <td class="nowrap">' . $HTML . '</td>
606  <td align="center">' . ($row['root'] ? $statusCheckedIcon : '') . '</td>
607  <td align="center">' . ($row['clConf'] ? $statusCheckedIcon : '') . '</td>
608  <td align="center">' . ($row['clConst'] ? $statusCheckedIcon : '') . '</td>
609  <td align="center">' . ($row['pid'] ?: '') . '</td>
610  <td align="center">' . ($RL >= 0 ? $RL : '') . '</td>
611  </tr>';
612  if ($deeper) {
613  $keyArray = $this->ext_getTemplateHierarchyArr($arr[$key . '.'], $depthData . ($first ? '' : '<span class="treeline-icon treeline-icon-' . $LN . '"></span>'), $keyArray);
614  }
615  }
616  return $keyArray;
617  }
618 
627  public function ext_process_hierarchyInfo(array $depthDataArr, &$pointer)
628  {
629  $parent = $this->hierarchyInfo[$pointer - 1]['templateParent'];
630  while ($pointer > 0 && $this->hierarchyInfo[$pointer - 1]['templateParent'] == $parent) {
631  $pointer--;
632  $row = $this->hierarchyInfo[$pointer];
633  $depthDataArr[$row['templateID']] = $row;
634  unset($this->clearList_setup_temp[$row['templateID']]);
635  unset($this->clearList_const_temp[$row['templateID']]);
636  $this->templateTitles[$row['templateID']] = $row['title'];
637  if ($row['templateID'] == ($this->hierarchyInfo[$pointer - 1]['templateParent'] ?? '')) {
638  $depthDataArr[$row['templateID'] . '.'] = $this->ext_process_hierarchyInfo([], $pointer);
639  }
640  }
641  return $depthDataArr;
642  }
643 
653  public function ext_getFirstTemplate($pid, $templateUid = 0)
654  {
655  if (empty($pid)) {
656  return null;
657  }
658 
659  // Query is taken from the runThroughTemplates($theRootLine) function in the parent class.
660  $queryBuilder = $this->getTemplateQueryBuilder($pid)
661  ->setMaxResults(1);
662  if ($templateUid) {
663  $queryBuilder->andWhere(
664  $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($templateUid, ‪Connection::PARAM_INT))
665  );
666  }
667  $row = $queryBuilder->executeQuery()->fetchAssociative();
668  BackendUtility::workspaceOL('sys_template', $row);
669 
670  return $row;
671  }
672 
679  public function ext_getAllTemplates($pid): array
680  {
681  if (empty($pid)) {
682  return [];
683  }
684  $result = $this->getTemplateQueryBuilder($pid)->executeQuery();
685  $outRes = [];
686  while ($row = $result->fetchAssociative()) {
687  BackendUtility::workspaceOL('sys_template', $row);
688  if (is_array($row)) {
689  $outRes[] = $row;
690  }
691  }
692  return $outRes;
693  }
694 
702  protected function getTemplateQueryBuilder(int $pid): QueryBuilder
703  {
704  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
705  ->getQueryBuilderForTable('sys_template');
706  $queryBuilder->getRestrictions()
707  ->removeAll()
708  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
709  ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->context->getAspect('workspace')->get('id')));
710 
711  $queryBuilder->select('*')
712  ->from('sys_template')
713  ->where(
714  $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, ‪Connection::PARAM_INT))
715  );
716  if (!empty(‪$GLOBALS['TCA']['sys_template']['ctrl']['sortby'])) {
717  $queryBuilder->orderBy(‪$GLOBALS['TCA']['sys_template']['ctrl']['sortby']);
718  }
719 
720  return $queryBuilder;
721  }
722 
726  public function ext_categorizeEditableConstants($editConstArray)
727  {
728  // Runs through the available constants and fills the $this->categories array with pointers and priority-info
729  foreach ($editConstArray as $constName => $constData) {
730  if (!$constData['type']) {
731  $constData['type'] = 'string';
732  }
733  $cats = explode(',', $constData['cat']);
734  // if = only one category, while allows for many. We have agreed on only one category is the most basic way...
735  foreach ($cats as $theCat) {
736  $theCat = trim($theCat);
737  if ($theCat) {
738  $this->categories[$theCat][$constName] = $constData['subcat'];
739  }
740  }
741  }
742  }
743 
747  public function ext_getCategoryLabelArray()
748  {
749  // Returns array used for labels in the menu.
750  $retArr = [];
751  foreach ($this->categories as $k => $v) {
752  if (!empty($v)) {
753  $retArr[$k] = strtoupper($k) . ' (' . count($v) . ')';
754  }
755  }
756  return $retArr;
757  }
758 
763  public function ext_getTypeData($type)
764  {
765  $retArr = [];
766  $type = trim($type);
767  if (!$type) {
768  $retArr['type'] = 'string';
769  } else {
770  $m = strcspn($type, ' [');
771  $retArr['type'] = strtolower(substr($type, 0, $m));
772  $types = ['int' => 1, 'options' => 1, 'file' => 1, 'boolean' => 1, 'offset' => 1, 'user' => 1];
773  if (isset($types[$retArr['type']])) {
774  $p = trim(substr($type, $m));
775  $reg = [];
776  preg_match('/\\[(.*)\\]/', $p, $reg);
777  $p = trim($reg[1] ?? '');
778  if ($p) {
779  $retArr['paramstr'] = $p;
780  switch ($retArr['type']) {
781  case 'int':
782  if ($retArr['paramstr'][0] === '-') {
783  $retArr['params'] = ‪GeneralUtility::intExplode('-', substr($retArr['paramstr'], 1));
784  $retArr['params'][0] = (int)('-' . $retArr['params'][0]);
785  } else {
786  $retArr['params'] = ‪GeneralUtility::intExplode('-', $retArr['paramstr']);
787  }
788  $retArr['min'] = $retArr['params'][0];
789  $retArr['max'] = $retArr['params'][1];
790  $retArr['paramstr'] = $retArr['params'][0] . ' - ' . $retArr['params'][1];
791  break;
792  case 'options':
793  $retArr['params'] = explode(',', $retArr['paramstr']);
794  break;
795  }
796  }
797  }
798  }
799  return $retArr;
800  }
801 
806  public function ext_fNandV($params)
807  {
808  $fN = 'data[' . $params['name'] . ']';
809  $idName = str_replace('.', '-', $params['name']);
810  $fV = $params['value'];
811  // Values entered from the constantsedit cannot be constants! 230502; removed \{ and set {
812  if (preg_match('/^{[\\$][a-zA-Z0-9\\.]*}$/', trim($fV), $reg)) {
813  $fV = '';
814  }
815  $fV = htmlspecialchars($fV);
816  return [$fN, $fV, $params, $idName];
817  }
818 
826  public function ext_printFields($theConstants, $category): array
827  {
828  reset($theConstants);
829  $groupedOutput = [];
830  $subcat = '';
831  if (!empty($this->categories[$category]) && is_array($this->categories[$category])) {
832  asort($this->categories[$category]);
833  $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
834  $categoryLoop = 0;
835  foreach ($this->categories[$category] as $name => $type) {
836  $params = $theConstants[$name];
837  if (is_array($params)) {
838  if ($subcat !== (string)($params['subcat_name'] ?? '')) {
839  $categoryLoop++;
840  $subcat = (string)($params['subcat_name'] ?? '');
841  $subcat_name = $subcat ? (string)($this->constantParser->getSubCategories()[$subcat][0] ?? '') : 'Others';
842  $groupedOutput[$categoryLoop] = [
843  'label' => $subcat_name,
844  'fields' => [],
845  ];
846  }
847  $label = $this->getLanguageService()->sL($params['label']);
848  $label_parts = explode(':', $label, 2);
849  if (count($label_parts) === 2) {
850  $head = trim($label_parts[0]);
851  $body = trim($label_parts[1]);
852  } else {
853  $head = trim($label_parts[0]);
854  $body = '';
855  }
856  $typeDat = $this->ext_getTypeData($params['type']);
857  $p_field = '';
858  $fragmentName = substr(md5($params['name']), 0, 10);
859  $fragmentNameEscaped = htmlspecialchars($fragmentName);
860  [$fN, $fV, $params, $idName] = $this->ext_fNandV($params);
861  $idName = htmlspecialchars($idName);
862  $hint = '';
863  switch ($typeDat['type']) {
864  case 'int':
865  case 'int+':
866  $additionalAttributes = '';
867  if ($typeDat['paramstr'] ?? false) {
868  $hint = ' Range: ' . $typeDat['paramstr'];
869  } elseif ($typeDat['type'] === 'int+') {
870  $hint = ' Range: 0 - ';
871  $typeDat['min'] = 0;
872  } else {
873  $hint = ' (Integer)';
874  }
875 
876  if (isset($typeDat['min'])) {
877  $additionalAttributes .= ' min="' . (int)$typeDat['min'] . '" ';
878  }
879  if (isset($typeDat['max'])) {
880  $additionalAttributes .= ' max="' . (int)$typeDat['max'] . '" ';
881  }
882 
883  $p_field =
884  '<input class="form-control" id="' . $idName . '" type="number"'
885  . ' name="' . $fN . '" value="' . $fV . '" data-form-update-fragment="' . $fragmentNameEscaped . '" ' . $additionalAttributes . ' />';
886  break;
887  case 'color':
888  $p_field = '
889  <input class="form-control formengine-colorpickerelement t3js-color-picker" type="text" id="input-' . $idName . '" rel="' . $idName .
890  '" name="' . $fN . '" value="' . $fV . '" data-form-update-fragment="' . $fragmentNameEscaped . '"/>';
891 
892  $this->javaScriptInstructions['color'] ??= ‪JavaScriptModuleInstruction::forRequireJS('TYPO3/CMS/Backend/ColorPicker')
893  ->invoke('initialize');
894  break;
895  case 'wrap':
896  $wArr = explode('|', $fV);
897  $p_field = '<div class="input-group">
898  <input class="form-control form-control-adapt" type="text" id="' . $idName . '" name="' . $fN . '" value="' . $wArr[0] . '" data-form-update-fragment="' . $fragmentNameEscaped . '" />
899  <span class="input-group-addon input-group-icon">|</span>
900  <input class="form-control form-control-adapt" type="text" name="W' . $fN . '" value="' . $wArr[1] . '" data-form-update-fragment="' . $fragmentNameEscaped . '" />
901  </div>';
902  break;
903  case 'offset':
904  $wArr = explode(',', $fV);
905  $labels = ‪GeneralUtility::trimExplode(',', $typeDat['paramstr']);
906  $p_field = '<span class="input-group-addon input-group-icon">' . ($labels[0] ?: 'x') . '</span><input type="text" class="form-control form-control-adapt" name="' . $fN . '" value="' . $wArr[0] . '" data-form-update-fragment="' . $fragmentNameEscaped . '" />';
907  $p_field .= '<span class="input-group-addon input-group-icon">' . ($labels[1] ?: 'y') . '</span><input type="text" name="W' . $fN . '" value="' . $wArr[1] . '" class="form-control form-control-adapt" data-form-update-fragment="' . $fragmentNameEscaped . '" />';
908  $labelsCount = count($labels);
909  for ($aa = 2; $aa < $labelsCount; $aa++) {
910  if ($labels[$aa]) {
911  $p_field .= '<span class="input-group-addon input-group-icon">' . $labels[$aa] . '</span><input type="text" name="W' . $aa . $fN . '" value="' . $wArr[$aa] . '" class="form-control form-control-adapt" data-form-update-fragment="' . $fragmentNameEscaped . '" />';
912  } else {
913  $p_field .= '<input type="hidden" name="W' . $aa . $fN . '" value="' . $wArr[$aa] . '" />';
914  }
915  }
916  $p_field = '<div class="input-group">' . $p_field . '</div>';
917  break;
918  case 'options':
919  if (is_array($typeDat['params'])) {
920  $p_field = '';
921  foreach ($typeDat['params'] as $val) {
922  $vParts = explode('=', $val, 2);
923  $label = $vParts[0];
924  $val = $vParts[1] ?? $vParts[0];
925  // option tag:
926  $sel = '';
927  if ($val === $params['value']) {
928  $sel = ' selected';
929  }
930  $p_field .= '<option value="' . htmlspecialchars($val) . '"' . $sel . '>' . $this->getLanguageService()->sL($label) . '</option>';
931  }
932  $p_field = '<select class="form-select" id="' . $idName . '" name="' . $fN . '" data-form-update-fragment="' . $fragmentNameEscaped . '">' . $p_field . '</select>';
933  }
934  break;
935  case 'boolean':
936  $sel = $fV ? 'checked' : '';
937  $p_field =
938  '<input type="hidden" name="' . $fN . '" value="0" />'
939  . '<label class="btn btn-default btn-checkbox">'
940  . '<input id="' . $idName . '" type="checkbox" name="' . $fN . '" value="' . (($typeDat['paramstr'] ?? false) ?: 1) . '" ' . $sel . ' data-form-update-fragment="' . $fragmentNameEscaped . '" />'
941  . '<span class="t3-icon fa"></span>'
942  . '</label>';
943  break;
944  case 'comment':
945  $sel = $fV ? '' : 'checked';
946  $p_field =
947  '<input type="hidden" name="' . $fN . '" value="" />'
948  . '<label class="btn btn-default btn-checkbox">'
949  . '<input id="' . $idName . '" type="checkbox" name="' . $fN . '" value="1" ' . $sel . ' data-form-update-fragment="' . $fragmentNameEscaped . '" />'
950  . '<span class="t3-icon fa"></span>'
951  . '</label>';
952  break;
953  case 'file':
954  // extensionlist
955  $extList = $typeDat['paramstr'];
956  if ($extList === 'IMAGE_EXT') {
957  $extList = ‪$GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'];
958  }
959  $p_field = '<option value="">(' . $extList . ')</option>';
960  if (trim($params['value'])) {
961  $val = $params['value'];
962  $p_field .= '<option value=""></option>';
963  $p_field .= '<option value="' . htmlspecialchars($val) . '" selected>' . $val . '</option>';
964  }
965  $p_field = '<select class="form-select" id="' . $idName . '" name="' . $fN . '" data-form-update-fragment="' . $fragmentNameEscaped . '">' . $p_field . '</select>';
966  break;
967  case 'user':
968  $userFunction = $typeDat['paramstr'];
969  $userFunctionParams = ['fieldName' => $fN, 'fieldValue' => $fV];
970  $p_field = GeneralUtility::callUserFunction($userFunction, $userFunctionParams, $this);
971  break;
972  default:
973  $p_field = '<input class="form-control" id="' . $idName . '" type="text" name="' . $fN . '" value="' . $fV . '" data-form-update-fragment="' . $fragmentNameEscaped . '" />';
974  }
975  // Define default names and IDs
976  $userTyposcriptID = 'userTS-' . $idName;
977  $defaultTyposcriptID = 'defaultTS-' . $idName;
978  $userTyposcriptStyle = '';
979  // Set the default styling options
980  if (isset($this->objReg[$params['name']])) {
981  $checkboxValue = 'checked';
982  $defaultTyposcriptStyle = 'style="display:none;"';
983  } else {
984  $checkboxValue = '';
985  $userTyposcriptStyle = 'style="display:none;"';
986  $defaultTyposcriptStyle = '';
987  }
988  $deleteIconHTML =
989  '<button type="button" class="btn btn-default t3js-toggle" data-bs-toggle="undo" rel="' . $idName . '">'
990  . '<span title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.deleteTitle')) . '">'
991  . $iconFactory->getIcon('actions-edit-undo', ‪Icon::SIZE_SMALL)->render()
992  . '</span>'
993  . '</button>';
994  $editIconHTML =
995  '<button type="button" class="btn btn-default t3js-toggle" data-bs-toggle="edit" rel="' . $idName . '">'
996  . '<span title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.editTitle')) . '">'
997  . $iconFactory->getIcon('actions-open', ‪Icon::SIZE_SMALL)->render()
998  . '</span>'
999  . '</button>';
1000  $constantCheckbox = '<input type="hidden" name="check[' . $params['name'] . ']" id="check-' . $idName . '" value="' . $checkboxValue . '"/>';
1001  // If there's no default value for the field, use a static label.
1002  if (!$params['default_value']) {
1003  $params['default_value'] = '[Empty]';
1004  }
1005  $constantDefaultRow =
1006  '<div class="input-group defaultTS" id="' . $defaultTyposcriptID . '" ' . $defaultTyposcriptStyle . '>'
1007  . '<span class="input-group-btn">' . $editIconHTML . '</span>'
1008  . '<input class="form-control" type="text" placeholder="' . htmlspecialchars($params['default_value']) . '" readonly>'
1009  . '</div>';
1010  $constantEditRow =
1011  '<div class="input-group userTS" id="' . $userTyposcriptID . '" ' . $userTyposcriptStyle . '>'
1012  . '<span class="input-group-btn">' . $deleteIconHTML . '</span>'
1013  . $p_field
1014  . '</div>';
1015  $constantData =
1016  $constantCheckbox
1017  . $constantEditRow
1018  . $constantDefaultRow;
1019 
1020  $groupedOutput[$categoryLoop]['items'][] = [
1021  'identifier' => $fragmentName,
1022  'label' => $head,
1023  'name' => $params['name'],
1024  'description' => $body,
1025  'hint' => $hint,
1026  'data' => $constantData,
1027  ];
1028  } else {
1029  ‪debug('Error. Constant did not exist. Should not happen.');
1030  }
1031  }
1032  }
1033  return $groupedOutput;
1034  }
1035 
1036  /***************************
1037  *
1038  * Processing input values
1039  *
1040  ***************************/
1044  public function ext_regObjectPositions(string $constants): void
1045  {
1046  // This runs through the lines of the constants-field of the active template and registers the constants-names
1047  // and line positions in an array, $this->objReg
1048  $this->raw = explode(LF, $constants);
1049  $this->rawP = 0;
1050  // Resetting the objReg if the divider is found!!
1051  $this->objReg = [];
1052  $this->ext_regObjects('');
1053  }
1054 
1058  public function ext_regObjects($pre)
1059  {
1060  // Works with regObjectPositions. "expands" the names of the TypoScript objects
1061  while (isset($this->raw[$this->rawP])) {
1062  $line = ltrim($this->raw[$this->rawP]);
1063  $this->rawP++;
1064  if ($line) {
1065  if ($line[0] === '[') {
1066  } elseif (strcspn($line, '}#/') != 0) {
1067  $varL = strcspn($line, ' {=<');
1068  $var = substr($line, 0, $varL);
1069  $line = ltrim(substr($line, $varL));
1070  switch ($line[0]) {
1071  case '=':
1072  $this->objReg[$pre . $var] = $this->rawP - 1;
1073  break;
1074  case '{':
1075  $this->ext_inBrace++;
1076  $this->ext_regObjects($pre . $var . '.');
1077  break;
1078  }
1079  $this->lastComment = '';
1080  } elseif ($line[0] === '}') {
1081  $this->lastComment = '';
1082  $this->ext_inBrace--;
1083  if ($this->ext_inBrace < 0) {
1084  $this->ext_inBrace = 0;
1085  } else {
1086  break;
1087  }
1088  }
1089  }
1090  }
1091  }
1092 
1097  public function ext_putValueInConf($key, $var)
1098  {
1099  // Puts the value $var to the TypoScript value $key in the current lines of the templates.
1100  // If the $key is not found in the template constants field, a new line is inserted in the bottom.
1101  $theValue = ' ' . trim($var);
1102  if (isset($this->objReg[$key])) {
1103  $lineNum = $this->objReg[$key];
1104  $parts = explode('=', $this->raw[$lineNum], 2);
1105  if (count($parts) === 2) {
1106  $parts[1] = $theValue;
1107  }
1108  $this->raw[$lineNum] = implode('=', $parts);
1109  } else {
1110  $this->raw[] = $key . ' =' . $theValue;
1111  }
1112  $this->changed = true;
1113  }
1114 
1118  public function ext_removeValueInConf($key)
1119  {
1120  // Removes the value in the configuration
1121  if (isset($this->objReg[$key])) {
1122  $lineNum = $this->objReg[$key];
1123  unset($this->raw[$lineNum]);
1124  }
1125  $this->changed = true;
1126  }
1127 
1133  public function ext_depthKeys($arr, $settings)
1134  {
1135  $tsbrArray = [];
1136  foreach ($arr as $theK => $theV) {
1137  $theKeyParts = explode('.', $theK);
1138  $depth = '';
1139  $c = count($theKeyParts);
1140  $a = 0;
1141  foreach ($theKeyParts as $p) {
1142  $a++;
1143  $depth .= ($depth ? '.' : '') . $p;
1144  $tsbrArray[$depth] = $c == $a ? $theV : 1;
1145  }
1146  }
1147  // Modify settings
1148  foreach ($tsbrArray as $theK => $theV) {
1149  if ($theV) {
1150  $settings[$theK] = 1;
1151  } else {
1152  unset($settings[$theK]);
1153  }
1154  }
1155  return $settings;
1156  }
1157 
1166  public function ext_procesInput($http_post_vars, $http_post_files, $theConstants, $tplRow)
1167  {
1168  $data = $http_post_vars['data'] ?? null;
1169  $check = $http_post_vars['check'] ?? [];
1170  $Wdata = $http_post_vars['Wdata'] ?? [];
1171  $W2data = $http_post_vars['W2data'] ?? [];
1172  $W3data = $http_post_vars['W3data'] ?? [];
1173  $W4data = $http_post_vars['W4data'] ?? [];
1174  $W5data = $http_post_vars['W5data'] ?? [];
1175  if (is_array($data)) {
1176  foreach ($data as $key => $var) {
1177  if (isset($theConstants[$key])) {
1178  // If checkbox is set, update the value
1179  if (isset($check[$key])) {
1180  // Exploding with linebreak, just to make sure that no multiline input is given!
1181  [$var] = explode(LF, $var);
1182  $typeDat = $this->ext_getTypeData($theConstants[$key]['type']);
1183  switch ($typeDat['type']) {
1184  case 'int':
1185  if ($typeDat['paramstr'] ?? false) {
1186  $var = ‪MathUtility::forceIntegerInRange((int)$var, $typeDat['params'][0], $typeDat['params'][1]);
1187  } else {
1188  $var = (int)$var;
1189  }
1190  break;
1191  case 'int+':
1192  $var = max(0, (int)$var);
1193  break;
1194  case 'color':
1195  $col = [];
1196  if ($var) {
1197  $var = preg_replace('/[^A-Fa-f0-9]*/', '', $var) ?? '';
1198  $useFulHex = strlen($var) > 3;
1199  $col[] = (int)hexdec($var[0]);
1200  $col[] = (int)hexdec($var[1]);
1201  $col[] = (int)hexdec($var[2]);
1202  if ($useFulHex) {
1203  $col[] = (int)hexdec($var[3]);
1204  $col[] = (int)hexdec($var[4]);
1205  $col[] = (int)hexdec($var[5]);
1206  }
1207  $var = substr('0' . dechex($col[0]), -1) . substr('0' . dechex($col[1]), -1) . substr('0' . dechex($col[2]), -1);
1208  if ($useFulHex) {
1209  $var .= substr('0' . dechex($col[3]), -1) . substr('0' . dechex($col[4]), -1) . substr('0' . dechex($col[5]), -1);
1210  }
1211  $var = '#' . strtoupper($var);
1212  }
1213  break;
1214  case 'comment':
1215  if ($var) {
1216  $var = '';
1217  } else {
1218  $var = '#';
1219  }
1220  break;
1221  case 'wrap':
1222  if (isset($Wdata[$key])) {
1223  $var .= '|' . $Wdata[$key];
1224  }
1225  break;
1226  case 'offset':
1227  if (isset($Wdata[$key])) {
1228  $var = (int)$var . ',' . (int)$Wdata[$key];
1229  if (isset($W2data[$key])) {
1230  $var .= ',' . (int)$W2data[$key];
1231  if (isset($W3data[$key])) {
1232  $var .= ',' . (int)$W3data[$key];
1233  if (isset($W4data[$key])) {
1234  $var .= ',' . (int)$W4data[$key];
1235  if (isset($W5data[$key])) {
1236  $var .= ',' . (int)$W5data[$key];
1237  }
1238  }
1239  }
1240  }
1241  }
1242  break;
1243  case 'boolean':
1244  if ($var) {
1245  $var = ($typeDat['paramstr'] ?? false) ?: 1;
1246  }
1247  break;
1248  }
1249  if ((string)($theConstants[$key]['value'] ?? '') !== (string)$var) {
1250  // Put value in, if changed.
1251  $this->ext_putValueInConf($key, $var);
1252  }
1253  // Remove the entry because it has been "used"
1254  unset($check[$key]);
1255  } else {
1256  $this->ext_removeValueInConf($key);
1257  }
1258  }
1259  }
1260  }
1261  // Remaining keys in $check indicates fields that are just clicked "on" to be edited.
1262  // Therefore we get the default value and puts that in the template as a start...
1263  foreach ($check ?? [] as $key => $var) {
1264  if (isset($theConstants[$key])) {
1265  $dValue = $theConstants[$key]['default_value'];
1266  $this->ext_putValueInConf($key, $dValue);
1267  }
1268  }
1269  }
1270 
1276  public function ext_prevPageWithTemplate($id, $perms_clause)
1277  {
1278  $rootLine = BackendUtility::BEgetRootLine($id, $perms_clause ? ' AND ' . $perms_clause : '');
1279  foreach ($rootLine as $p) {
1280  if ($this->ext_getFirstTemplate($p['uid'])) {
1281  return $p;
1282  }
1283  }
1284  return [];
1285  }
1286 
1292  protected function getRootLine()
1293  {
1294  return is_array($this->absoluteRootLine) ? $this->absoluteRootLine : [];
1295  }
1296 
1300  protected function getLanguageService()
1301  {
1302  return ‪$GLOBALS['LANG'];
1303  }
1304 }
‪TYPO3\CMS\Core\Utility\ArrayUtility\flatten
‪static array flatten(array $array, $prefix='', bool $keepDots=false)
Definition: ArrayUtility.php:486
‪TYPO3\CMS\Core\Imaging\Icon\SIZE_SMALL
‪const SIZE_SMALL
Definition: Icon.php:30
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:999
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT
‪const PARAM_INT
Definition: Connection.php:49
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger($var)
Definition: MathUtility.php:74
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser
Definition: TypoScriptParser.php:36
‪TYPO3\CMS\Core\Imaging\Icon
Definition: Icon.php:26
‪TYPO3\CMS\Core\Utility\MathUtility\forceIntegerInRange
‪static int forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
Definition: MathUtility.php:32
‪TYPO3\CMS\Core\Exception
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:34
‪TYPO3\CMS\Core\Page\JavaScriptModuleInstruction
Definition: JavaScriptModuleInstruction.php:23
‪TYPO3\CMS\Core\Context\Context
Definition: Context.php:53
‪TYPO3\CMS\Core\TypoScript
Definition: ExtendedTemplateService.php:16
‪TYPO3\CMS\Backend\Routing\UriBuilder
Definition: UriBuilder.php:40
‪TYPO3\CMS\Core\Page\JavaScriptModuleInstruction\forRequireJS
‪static self forRequireJS(string $name, ?string $exportName=null)
Definition: JavaScriptModuleInstruction.php:49
‪TYPO3\CMS\Frontend\Configuration\TypoScript\ConditionMatching\ConditionMatcher
Definition: ConditionMatcher.php:30
‪debug
‪debug($variable='', $title=null, $group=null)
Definition: GlobalDebugFunctions.php:19
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:38
‪TYPO3\CMS\Core\TypoScript\TemplateService
Definition: TemplateService.php:46
‪TYPO3\CMS\Core\Utility\ArrayUtility
Definition: ArrayUtility.php:24
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction
Definition: DeletedRestriction.php:28
‪TYPO3\CMS\Core\TypoScript\Parser\ConstantConfigurationParser
Definition: ConstantConfigurationParser.php:34
‪TYPO3\CMS\Core\Utility\GeneralUtility\intExplode
‪static int[] intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:927
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:22
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:42
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:50
‪TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction
Definition: WorkspaceRestriction.php:40