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 16namespace TYPO3\CMS\Core\TypoScript; 17 18use TYPO3\CMS\Backend\Routing\UriBuilder; 19use TYPO3\CMS\Backend\Utility\BackendUtility; 20use TYPO3\CMS\Core\Context\Context; 21use TYPO3\CMS\Core\Database\ConnectionPool; 22use TYPO3\CMS\Core\Database\Query\QueryBuilder; 23use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction; 24use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction; 25use TYPO3\CMS\Core\Exception; 26use TYPO3\CMS\Core\Imaging\Icon; 27use TYPO3\CMS\Core\Imaging\IconFactory; 28use TYPO3\CMS\Core\Localization\LanguageService; 29use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction; 30use TYPO3\CMS\Core\TypoScript\Parser\ConstantConfigurationParser; 31use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser; 32use TYPO3\CMS\Core\Utility\ArrayUtility; 33use TYPO3\CMS\Core\Utility\GeneralUtility; 34use TYPO3\CMS\Core\Utility\MathUtility; 35use TYPO3\CMS\Frontend\Configuration\TypoScript\ConditionMatching\ConditionMatcher; 36 37/** 38 * TSParser extension class to TemplateService 39 * Contains functions for the TS module in TYPO3 backend 40 * 41 * @internal this is only used for the TYPO3 TypoScript Template module, which should not be used in Extensions 42 */ 43class ExtendedTemplateService extends TemplateService 44{ 45 /** 46 * @var array 47 */ 48 protected $categories = [ 49 'basic' => [], 50 // 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. 51 'menu' => [], 52 // Menu setup. This includes fontfiles, sizes, background images. Depending on the menutype. 53 'content' => [], 54 // All constants related to the display of pagecontent elements 55 'page' => [], 56 // General configuration like metatags, link targets 57 'advanced' => [], 58 // Advanced functions, which are used very seldom. 59 'all' => [], 60 ]; 61 62 /** 63 * Tsconstanteditor 64 * 65 * @var int 66 */ 67 public $ext_inBrace = 0; 68 69 /** 70 * Tsbrowser 71 * 72 * @var array 73 */ 74 public $tsbrowser_searchKeys = []; 75 76 /** 77 * @var array 78 */ 79 public $tsbrowser_depthKeys = []; 80 81 /** 82 * @var string 83 */ 84 public $constantMode = ''; 85 86 /** 87 * @var string 88 */ 89 public $regexMode = ''; 90 91 /** 92 * @var int 93 */ 94 public $ext_expandAllNotes = 0; 95 96 /** 97 * @var int 98 */ 99 public $ext_noPMicons = 0; 100 101 /** 102 * Ts analyzer 103 * 104 * @var array 105 */ 106 public $templateTitles = []; 107 108 /** 109 * @var array|null 110 */ 111 protected $lnToScript; 112 113 /** 114 * @var array 115 */ 116 public $clearList_const_temp; 117 118 /** 119 * @var array 120 */ 121 public $clearList_setup_temp; 122 123 /** 124 * @var string 125 */ 126 public $bType = ''; 127 128 /** 129 * @var bool 130 */ 131 public $linkObjects = false; 132 133 /** 134 * @var bool 135 */ 136 public $changed = false; 137 138 /** 139 * @var int[] 140 */ 141 protected $objReg = []; 142 143 /** 144 * @var array 145 */ 146 public $raw = []; 147 148 /** 149 * @var int 150 */ 151 public $rawP = 0; 152 153 /** 154 * @var string 155 */ 156 public $lastComment = ''; 157 158 /** 159 * @var array<string, JavaScriptModuleInstruction> 160 */ 161 protected $javaScriptInstructions = []; 162 163 /** 164 * @var \TYPO3\CMS\Core\TypoScript\Parser\ConstantConfigurationParser 165 */ 166 private $constantParser; 167 168 /** 169 * @param Context|null $context 170 * @param \TYPO3\CMS\Core\TypoScript\Parser\ConstantConfigurationParser $constantParser 171 */ 172 public function __construct(Context $context = null, ConstantConfigurationParser $constantParser = null) 173 { 174 parent::__construct($context); 175 $this->constantParser = $constantParser ?? GeneralUtility::makeInstance(ConstantConfigurationParser::class); 176 // Disabled in backend context 177 $this->tt_track = false; 178 $this->verbose = false; 179 } 180 181 /** 182 * @return array<string, JavaScriptModuleInstruction> 183 */ 184 public function getJavaScriptInstructions(): array 185 { 186 return $this->javaScriptInstructions; 187 } 188 189 /** 190 * Substitute constant 191 * 192 * @param string $all 193 * @return string 194 */ 195 public function substituteConstants($all) 196 { 197 return preg_replace_callback('/\\{\\$(.[^}]+)\\}/', [$this, 'substituteConstantsCallBack'], $all); 198 } 199 200 /** 201 * Call back method for preg_replace_callback in substituteConstants 202 * 203 * @param array $matches Regular expression matches 204 * @return string Replacement 205 * @see substituteConstants() 206 */ 207 public function substituteConstantsCallBack($matches) 208 { 209 $marker = substr(md5($matches[0]), 0, 6); 210 switch ($this->constantMode) { 211 case 'const': 212 $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]; 213 break; 214 case 'subst': 215 $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]; 216 break; 217 case 'untouched': 218 $ret_val = $matches[0]; 219 break; 220 default: 221 $ret_val = isset($this->flatSetup[$matches[1]]) && !is_array($this->flatSetup[$matches[1]]) ? $this->flatSetup[$matches[1]] : $matches[0]; 222 } 223 return $ret_val; 224 } 225 226 /** 227 * Substitute markers added in substituteConstantsCallBack() 228 * with ##6chars_B##value1##6chars_M##value2##6chars_E## 229 * 230 * @param string $all 231 * @return string 232 */ 233 public function substituteCMarkers($all) 234 { 235 switch ($this->constantMode) { 236 case 'const': 237 case 'subst': 238 $all = preg_replace( 239 '/##[a-z0-9]{6}_B##(.*?)##[a-z0-9]{6}_M##(.*?)##[a-z0-9]{6}_E##/', 240 '<strong class="text-success" data-bs-toggle="tooltip" data-bs-placement="top" data-title="$1" title="$1">$2</strong>', 241 $all 242 ); 243 break; 244 default: 245 } 246 return $all; 247 } 248 249 /** 250 * Parse constants with respect to the constant-editor in this module. 251 * In particular comments in the code are registered and the edit_divider is taken into account. 252 * 253 * @return array 254 */ 255 public function generateConfig_constants() 256 { 257 // Parse constants 258 $constants = GeneralUtility::makeInstance(TypoScriptParser::class); 259 // Register comments! 260 $constants->regComments = true; 261 /** @var ConditionMatcher $matchObj */ 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 283 /** 284 * @param array $theSetup 285 * @param string $theKey 286 * @return array{0: array, 1: string} 287 */ 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 311 /** 312 * Get object tree 313 * 314 * @param array $arr 315 * @param string $depth_in 316 * @param string $depthData 317 * @param string $parentType (unused) 318 * @param string $parentValue (unused) 319 * @param string $alphaSort sorts the array keys / tree by alphabet when set to 1 320 * @return string 321 */ 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 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */ 331 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); 332 foreach ($arr as $key => $value) { 333 // Don't do anything with comments / linenumber registrations... 334 if (substr($key, -2) !== '..') { 335 $key = preg_replace('/\\.$/', '', $key) ?? ''; 336 if (substr($key, -1) !== '.') { 337 if (MathUtility::canBeInterpretedAsInteger($key)) { 338 $keyArr_num[$key] = $arr[$key] ?? ''; 339 } else { 340 $keyArr_alpha[$key] = $arr[$key] ?? ''; 341 } 342 } 343 } 344 } 345 ksort($keyArr_num); 346 $keyArr = $keyArr_num + $keyArr_alpha; 347 if ($depth_in) { 348 $depth_in = $depth_in . '.'; 349 } 350 foreach ($keyArr as $key => $value) { 351 $depth = $depth_in . $key; 352 // This excludes all constants starting with '_' from being shown. 353 if ($this->bType !== 'const' || $depth[0] !== '_') { 354 $goto = substr(md5($depth), 0, 6); 355 $deeper = is_array($arr[$key . '.'] ?? null) && (($this->tsbrowser_depthKeys[$depth] ?? false) || $this->ext_expandAllNotes); 356 $PM = is_array($arr[$key . '.'] ?? null) && !$this->ext_noPMicons ? ($deeper ? 'minus' : 'plus') : 'join'; 357 $HTML .= $depthData . '<li><span class="list-tree-group">'; 358 if ($PM !== 'join') { 359 $urlParameters = [ 360 'id' => (int)GeneralUtility::_GP('id'), 361 'tsbr[' . $depth . ']' => $deeper ? 0 : 1, 362 ]; 363 $aHref = (string)$uriBuilder->buildUriFromRoute('web_ts', $urlParameters) . '#' . $goto; 364 $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>'; 365 } 366 $label = $key; 367 // Read only... 368 if (($depth === 'types') && $this->bType === 'setup') { 369 $label = '<span style="color: #666666;">' . $label . '</span>'; 370 } else { 371 if ($this->linkObjects) { 372 $urlParameters = [ 373 'id' => (int)GeneralUtility::_GP('id'), 374 'sObj' => $depth, 375 ]; 376 $aHref = (string)$uriBuilder->buildUriFromRoute('web_ts', $urlParameters); 377 if ($this->bType !== 'const') { 378 $ln = is_array($arr[$key . '.ln..'] ?? null) ? 'Defined in: ' . $this->lineNumberToScript($arr[$key . '.ln..']) : 'N/A'; 379 } else { 380 $ln = ''; 381 } 382 if (($this->tsbrowser_searchKeys[$depth] ?? 0) & 4) { 383 // The key has matched the search string 384 $label = '<strong class="text-danger">' . $label . '</strong>'; 385 } 386 $label = '<a href="' . htmlspecialchars($aHref) . '" title="' . htmlspecialchars($depth_in . $key . ' ' . $ln) . '">' . $label . '</a>'; 387 } 388 } 389 $HTML .= '<span class="list-tree-label" title="' . htmlspecialchars($depth_in . $key) . '">[' . $label . ']</span>'; 390 if (isset($arr[$key])) { 391 $theValue = $arr[$key]; 392 // The value has matched the search string 393 if (($this->tsbrowser_searchKeys[$depth] ?? 0) & 2) { 394 $HTML .= ' = <span class="list-tree-value text-danger">' . htmlspecialchars($theValue) . '</span>'; 395 } else { 396 $HTML .= ' = <span class="list-tree-value">' . htmlspecialchars($theValue) . '</span>'; 397 } 398 if ($this->ext_regComments && isset($arr[$key . '..'])) { 399 $comment = (string)$arr[$key . '..']; 400 // Skip INCLUDE_TYPOSCRIPT comments, they are almost useless 401 if (!preg_match('/### <INCLUDE_TYPOSCRIPT:.*/', $comment)) { 402 // Remove linebreaks, replace with ' ' 403 $comment = preg_replace('/[\\r\\n]/', ' ', $comment) ?? ''; 404 // Remove # and * if more than twice in a row 405 $comment = preg_replace('/[#\\*]{2,}/', '', $comment) ?? ''; 406 // Replace leading # (just if it exists) and add it again. Result: Every comment should be prefixed by a '#'. 407 $comment = preg_replace('/^[#\\*\\s]+/', '# ', $comment) ?? ''; 408 // Masking HTML Tags: Replace < with < and > with > 409 $comment = htmlspecialchars($comment); 410 $HTML .= ' <i class="text-muted">' . trim($comment) . '</i>'; 411 } 412 } 413 } 414 $HTML .= '</span>'; 415 if ($deeper) { 416 $HTML .= $this->ext_getObjTree($arr[$key . '.'] ?? [], $depth, $depthData, '', $arr[$key] ?? '', $alphaSort); 417 } 418 } 419 } 420 if ($HTML !== '') { 421 $HTML = '<ul class="list-tree text-monospace">' . $HTML . '</ul>'; 422 } 423 424 return $HTML; 425 } 426 427 /** 428 * Find the originating template name for an array of line numbers (TypoScript setup only!) 429 * Given an array of linenumbers the method will try to find the corresponding template where this line originated 430 * The linenumber indicates the *last* lineNumber that is part of the template 431 * 432 * lineNumbers are in sync with the calculated lineNumbers '.ln..' in TypoScriptParser 433 * 434 * @param array $lnArr Array with linenumbers (might have some extra symbols, for example for unsetting) to be processed 435 * @return string Imploded array of line number and template title 436 */ 437 public function lineNumberToScript(array $lnArr) 438 { 439 // On the first call, construct the lnToScript array. 440 if (!is_array($this->lnToScript)) { 441 $this->lnToScript = []; 442 443 // aggregatedTotalLineCount 444 $c = 0; 445 foreach ($this->hierarchyInfo as $templateNumber => $info) { 446 // hierarchyInfo has the number of lines in configLines, but unfortunately this value 447 // was calculated *before* processing of any INCLUDE instructions 448 // for some yet unknown reason we have to add an extra +2 offset 449 $linecountAfterIncludeProcessing = substr_count($this->config[$templateNumber], LF) + 2; 450 $c += $linecountAfterIncludeProcessing; 451 $this->lnToScript[$c] = $info['title']; 452 } 453 } 454 455 foreach ($lnArr as $k => $ln) { 456 foreach ($this->lnToScript as $endLn => $title) { 457 if ($endLn >= (int)$ln) { 458 $lnArr[$k] = '"' . $title . '", ' . $ln; 459 break; 460 } 461 } 462 } 463 464 return implode('; ', $lnArr); 465 } 466 467 /** 468 * @param array $arr 469 * @param string $depth_in 470 * @param string $searchString 471 * @param array $keyArray 472 * @return array 473 * @throws Exception 474 */ 475 public function ext_getSearchKeys($arr, $depth_in, $searchString, $keyArray) 476 { 477 $keyArr = []; 478 foreach ($arr as $key => $value) { 479 $key = preg_replace('/\\.$/', '', $key) ?? ''; 480 if (substr($key, -1) !== '.') { 481 $keyArr[$key] = 1; 482 } 483 } 484 if ($depth_in) { 485 $depth_in = $depth_in . '.'; 486 } 487 $searchPattern = ''; 488 if ($this->regexMode) { 489 $searchPattern = '/' . addcslashes($searchString, '/') . '/'; 490 $matchResult = @preg_match($searchPattern, ''); 491 if ($matchResult === false) { 492 throw new Exception(sprintf('Error evaluating regular expression "%s".', $searchPattern), 1446559458); 493 } 494 } 495 foreach ($keyArr as $key => $value) { 496 $depth = $depth_in . $key; 497 if ($this->regexMode) { 498 // The value has matched 499 if (($arr[$key] ?? false) && preg_match($searchPattern, $arr[$key])) { 500 $this->tsbrowser_searchKeys[$depth] = ($this->tsbrowser_searchKeys[$depth] ?? 0) + 2; 501 } 502 // The key has matched 503 if (preg_match($searchPattern, $key)) { 504 $this->tsbrowser_searchKeys[$depth] = ($this->tsbrowser_searchKeys[$depth] ?? 0) + 4; 505 } 506 // Just open this subtree if the parent key has matched the search 507 if (preg_match($searchPattern, $depth_in)) { 508 $this->tsbrowser_searchKeys[$depth] = 1; 509 } 510 } else { 511 // The value has matched 512 if (($arr[$key] ?? false) && stripos($arr[$key], $searchString) !== false) { 513 $this->tsbrowser_searchKeys[$depth] = ($this->tsbrowser_searchKeys[$depth] ?? 0) + 2; 514 } 515 // The key has matches 516 if (stripos($key, $searchString) !== false) { 517 $this->tsbrowser_searchKeys[$depth] = ($this->tsbrowser_searchKeys[$depth] ?? 0) + 4; 518 } 519 // Just open this subtree if the parent key has matched the search 520 if (stripos($depth_in, $searchString) !== false) { 521 $this->tsbrowser_searchKeys[$depth] = 1; 522 } 523 } 524 if (is_array($arr[$key . '.'] ?? null)) { 525 $cS = count($this->tsbrowser_searchKeys); 526 $keyArray = $this->ext_getSearchKeys($arr[$key . '.'], $depth, $searchString, $keyArray); 527 if ($cS !== count($this->tsbrowser_searchKeys)) { 528 $keyArray[$depth] = 1; 529 } 530 } 531 } 532 return $keyArray; 533 } 534 535 /** 536 * @param int $pid 537 * @return int 538 */ 539 public function ext_getRootlineNumber($pid) 540 { 541 if ($pid) { 542 foreach ($this->getRootLine() as $key => $val) { 543 if ((int)$val['uid'] === (int)$pid) { 544 return (int)$key; 545 } 546 } 547 } 548 return -1; 549 } 550 551 /** 552 * @param array $arr 553 * @param string $depthData 554 * @param array $keyArray 555 * @param int $first 556 * @return array 557 */ 558 public function ext_getTemplateHierarchyArr($arr, $depthData, $keyArray, $first = 0) 559 { 560 $keyArr = []; 561 foreach ($arr as $key => $value) { 562 $key = preg_replace('/\\.$/', '', $key) ?? ''; 563 if (substr($key, -1) !== '.') { 564 $keyArr[$key] = 1; 565 } 566 } 567 $a = 0; 568 $c = count($keyArr); 569 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */ 570 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); 571 /** @var IconFactory $iconFactory */ 572 $iconFactory = GeneralUtility::makeInstance(IconFactory::class); 573 foreach ($keyArr as $key => $value) { 574 $HTML = ''; 575 $a++; 576 $deeper = is_array($arr[$key . '.'] ?? false); 577 $row = $arr[$key]; 578 $LN = $a == $c ? 'blank' : 'line'; 579 $BTM = $a == $c ? 'top' : ''; 580 $HTML .= $depthData; 581 $alttext = '[' . $row['templateID'] . ']'; 582 $alttext .= $row['pid'] ? ' - ' . BackendUtility::getRecordPath($row['pid'], '1=1', 20) : ''; 583 $icon = strpos($row['templateID'], 'sys') === 0 584 ? '<span title="' . htmlspecialchars($alttext) . '">' . $iconFactory->getIconForRecord('sys_template', $row, Icon::SIZE_SMALL)->render() . '</span>' 585 : '<span title="' . htmlspecialchars($alttext) . '">' . $iconFactory->getIcon('mimetypes-x-content-template-static', Icon::SIZE_SMALL)->render() . '</span>'; 586 if (in_array($row['templateID'], $this->clearList_const) || in_array($row['templateID'], $this->clearList_setup)) { 587 $urlParameters = [ 588 'id' => (int)GeneralUtility::_GP('id'), 589 'template' => $row['templateID'], 590 ]; 591 $aHref = (string)$uriBuilder->buildUriFromRoute('web_ts', $urlParameters); 592 $A_B = '<a href="' . htmlspecialchars($aHref) . '">'; 593 $A_E = '</a>'; 594 if (GeneralUtility::_GP('template') == $row['templateID']) { 595 $A_B = '<strong>' . $A_B; 596 $A_E .= '</strong>'; 597 } 598 } else { 599 $A_B = ''; 600 $A_E = ''; 601 } 602 $HTML .= ($first ? '' : '<span class="treeline-icon treeline-icon-join' . $BTM . '"></span>') . $icon . ' ' . $A_B 603 . htmlspecialchars(GeneralUtility::fixed_lgd_cs($row['title'], $GLOBALS['BE_USER']->uc['titleLen'])) 604 . $A_E . ' '; 605 $RL = $this->ext_getRootlineNumber($row['pid']); 606 $statusCheckedIcon = $iconFactory->getIcon('status-status-checked', Icon::SIZE_SMALL)->render(); 607 $keyArray[] = '<tr> 608 <td class="nowrap">' . $HTML . '</td> 609 <td align="center">' . ($row['root'] ? $statusCheckedIcon : '') . '</td> 610 <td align="center">' . ($row['clConf'] ? $statusCheckedIcon : '') . '</td> 611 <td align="center">' . ($row['clConst'] ? $statusCheckedIcon : '') . '</td> 612 <td align="center">' . ($row['pid'] ?: '') . '</td> 613 <td align="center">' . ($RL >= 0 ? $RL : '') . '</td> 614 </tr>'; 615 if ($deeper) { 616 $keyArray = $this->ext_getTemplateHierarchyArr($arr[$key . '.'], $depthData . ($first ? '' : '<span class="treeline-icon treeline-icon-' . $LN . '"></span>'), $keyArray); 617 } 618 } 619 return $keyArray; 620 } 621 622 /** 623 * Processes the flat array from TemplateService->hierarchyInfo 624 * and turns it into a hierarchical array to show dependencies (used by TemplateAnalyzer) 625 * 626 * @param array $depthDataArr (empty array on external call) 627 * @param int $pointer Element number (1! to count()) of $this->hierarchyInfo that should be processed. 628 * @return array Processed hierachyInfo. 629 */ 630 public function ext_process_hierarchyInfo(array $depthDataArr, &$pointer) 631 { 632 $parent = $this->hierarchyInfo[$pointer - 1]['templateParent']; 633 while ($pointer > 0 && $this->hierarchyInfo[$pointer - 1]['templateParent'] == $parent) { 634 $pointer--; 635 $row = $this->hierarchyInfo[$pointer]; 636 $depthDataArr[$row['templateID']] = $row; 637 unset($this->clearList_setup_temp[$row['templateID']]); 638 unset($this->clearList_const_temp[$row['templateID']]); 639 $this->templateTitles[$row['templateID']] = $row['title']; 640 if ($row['templateID'] == ($this->hierarchyInfo[$pointer - 1]['templateParent'] ?? '')) { 641 $depthDataArr[$row['templateID'] . '.'] = $this->ext_process_hierarchyInfo([], $pointer); 642 } 643 } 644 return $depthDataArr; 645 } 646 647 /** 648 * Get a single sys_template record attached to a single page. 649 * If multiple template records are on this page, the first (order by sorting) 650 * record will be returned, unless a specific template uid is specified via $templateUid 651 * 652 * @param int $pid The pid to select sys_template records from 653 * @param int $templateUid Optional template uid 654 * @return array|null Returns the template record or null if none was found 655 */ 656 public function ext_getFirstTemplate($pid, $templateUid = 0) 657 { 658 if (empty($pid)) { 659 return null; 660 } 661 662 // Query is taken from the runThroughTemplates($theRootLine) function in the parent class. 663 $queryBuilder = $this->getTemplateQueryBuilder($pid) 664 ->setMaxResults(1); 665 if ($templateUid) { 666 $queryBuilder->andWhere( 667 $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($templateUid, \PDO::PARAM_INT)) 668 ); 669 } 670 $row = $queryBuilder->executeQuery()->fetchAssociative(); 671 BackendUtility::workspaceOL('sys_template', $row); 672 673 return $row; 674 } 675 676 /** 677 * Get an array of all template records on a page. 678 * 679 * @param int $pid Pid to fetch sys_template records for 680 * @return array[] Array of template records 681 */ 682 public function ext_getAllTemplates($pid): array 683 { 684 if (empty($pid)) { 685 return []; 686 } 687 $result = $this->getTemplateQueryBuilder($pid)->executeQuery(); 688 $outRes = []; 689 while ($row = $result->fetchAssociative()) { 690 BackendUtility::workspaceOL('sys_template', $row); 691 if (is_array($row)) { 692 $outRes[] = $row; 693 } 694 } 695 return $outRes; 696 } 697 698 /** 699 * Internal helper method to prepare the query builder for 700 * getting sys_template records from a given pid 701 * 702 * @param int $pid The pid to select sys_template records from 703 * @return QueryBuilder Returns a QueryBuilder 704 */ 705 protected function getTemplateQueryBuilder(int $pid): QueryBuilder 706 { 707 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) 708 ->getQueryBuilderForTable('sys_template'); 709 $queryBuilder->getRestrictions() 710 ->removeAll() 711 ->add(GeneralUtility::makeInstance(DeletedRestriction::class)) 712 ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $GLOBALS['BE_USER']->workspace)); 713 714 $queryBuilder->select('*') 715 ->from('sys_template') 716 ->where( 717 $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)) 718 ); 719 if (!empty($GLOBALS['TCA']['sys_template']['ctrl']['sortby'])) { 720 $queryBuilder->orderBy($GLOBALS['TCA']['sys_template']['ctrl']['sortby']); 721 } 722 723 return $queryBuilder; 724 } 725 726 /** 727 * @param array $editConstArray 728 */ 729 public function ext_categorizeEditableConstants($editConstArray) 730 { 731 // Runs through the available constants and fills the $this->categories array with pointers and priority-info 732 foreach ($editConstArray as $constName => $constData) { 733 if (!$constData['type']) { 734 $constData['type'] = 'string'; 735 } 736 $cats = explode(',', $constData['cat']); 737 // if = only one category, while allows for many. We have agreed on only one category is the most basic way... 738 foreach ($cats as $theCat) { 739 $theCat = trim($theCat); 740 if ($theCat) { 741 $this->categories[$theCat][$constName] = $constData['subcat']; 742 } 743 } 744 } 745 } 746 747 /** 748 * @return array 749 */ 750 public function ext_getCategoryLabelArray() 751 { 752 // Returns array used for labels in the menu. 753 $retArr = []; 754 foreach ($this->categories as $k => $v) { 755 if (!empty($v)) { 756 $retArr[$k] = strtoupper($k) . ' (' . count($v) . ')'; 757 } 758 } 759 return $retArr; 760 } 761 762 /** 763 * @param string $type 764 * @return array 765 */ 766 public function ext_getTypeData($type) 767 { 768 $retArr = []; 769 $type = trim($type); 770 if (!$type) { 771 $retArr['type'] = 'string'; 772 } else { 773 $m = strcspn($type, ' ['); 774 $retArr['type'] = strtolower(substr($type, 0, $m)); 775 $types = ['int' => 1, 'options' => 1, 'file' => 1, 'boolean' => 1, 'offset' => 1, 'user' => 1]; 776 if (isset($types[$retArr['type']])) { 777 $p = trim(substr($type, $m)); 778 $reg = []; 779 preg_match('/\\[(.*)\\]/', $p, $reg); 780 $p = trim($reg[1] ?? ''); 781 if ($p) { 782 $retArr['paramstr'] = $p; 783 switch ($retArr['type']) { 784 case 'int': 785 if ($retArr['paramstr'][0] === '-') { 786 $retArr['params'] = GeneralUtility::intExplode('-', substr($retArr['paramstr'], 1)); 787 $retArr['params'][0] = (int)('-' . $retArr['params'][0]); 788 } else { 789 $retArr['params'] = GeneralUtility::intExplode('-', $retArr['paramstr']); 790 } 791 $retArr['min'] = $retArr['params'][0]; 792 $retArr['max'] = $retArr['params'][1]; 793 $retArr['paramstr'] = $retArr['params'][0] . ' - ' . $retArr['params'][1]; 794 break; 795 case 'options': 796 $retArr['params'] = explode(',', $retArr['paramstr']); 797 break; 798 } 799 } 800 } 801 } 802 return $retArr; 803 } 804 805 /** 806 * @param array $params 807 * @return array 808 */ 809 public function ext_fNandV($params) 810 { 811 $fN = 'data[' . $params['name'] . ']'; 812 $idName = str_replace('.', '-', $params['name']); 813 $fV = $params['value']; 814 // Values entered from the constantsedit cannot be constants! 230502; removed \{ and set { 815 if (preg_match('/^{[\\$][a-zA-Z0-9\\.]*}$/', trim($fV), $reg)) { 816 $fV = ''; 817 } 818 $fV = htmlspecialchars($fV); 819 return [$fN, $fV, $params, $idName]; 820 } 821 822 /** 823 * This functions returns the HTML-code that creates the editor-layout of the module. 824 * 825 * @param array $theConstants 826 * @param string $category 827 * @return array 828 */ 829 public function ext_printFields($theConstants, $category): array 830 { 831 reset($theConstants); 832 $groupedOutput = []; 833 $subcat = ''; 834 if (!empty($this->categories[$category]) && is_array($this->categories[$category])) { 835 asort($this->categories[$category]); 836 /** @var IconFactory $iconFactory */ 837 $iconFactory = GeneralUtility::makeInstance(IconFactory::class); 838 $categoryLoop = 0; 839 foreach ($this->categories[$category] as $name => $type) { 840 $params = $theConstants[$name]; 841 if (is_array($params)) { 842 if ($subcat !== (string)($params['subcat_name'] ?? '')) { 843 $categoryLoop++; 844 $subcat = (string)($params['subcat_name'] ?? ''); 845 $subcat_name = $subcat ? (string)($this->constantParser->getSubCategories()[$subcat][0] ?? '') : 'Others'; 846 $groupedOutput[$categoryLoop] = [ 847 'label' => $subcat_name, 848 'fields' => [], 849 ]; 850 } 851 $label = $this->getLanguageService()->sL($params['label']); 852 $label_parts = explode(':', $label, 2); 853 if (count($label_parts) === 2) { 854 $head = trim($label_parts[0]); 855 $body = trim($label_parts[1]); 856 } else { 857 $head = trim($label_parts[0]); 858 $body = ''; 859 } 860 $typeDat = $this->ext_getTypeData($params['type']); 861 $p_field = ''; 862 $fragmentName = substr(md5($params['name']), 0, 10); 863 $fragmentNameEscaped = htmlspecialchars($fragmentName); 864 [$fN, $fV, $params, $idName] = $this->ext_fNandV($params); 865 $idName = htmlspecialchars($idName); 866 $hint = ''; 867 switch ($typeDat['type']) { 868 case 'int': 869 case 'int+': 870 $additionalAttributes = ''; 871 if ($typeDat['paramstr'] ?? false) { 872 $hint = ' Range: ' . $typeDat['paramstr']; 873 } elseif ($typeDat['type'] === 'int+') { 874 $hint = ' Range: 0 - '; 875 $typeDat['min'] = 0; 876 } else { 877 $hint = ' (Integer)'; 878 } 879 880 if (isset($typeDat['min'])) { 881 $additionalAttributes .= ' min="' . (int)$typeDat['min'] . '" '; 882 } 883 if (isset($typeDat['max'])) { 884 $additionalAttributes .= ' max="' . (int)$typeDat['max'] . '" '; 885 } 886 887 $p_field = 888 '<input class="form-control" id="' . $idName . '" type="number"' 889 . ' name="' . $fN . '" value="' . $fV . '" data-form-update-fragment="' . $fragmentNameEscaped . '" ' . $additionalAttributes . ' />'; 890 break; 891 case 'color': 892 $p_field = ' 893 <input class="form-control formengine-colorpickerelement t3js-color-picker" type="text" id="input-' . $idName . '" rel="' . $idName . 894 '" name="' . $fN . '" value="' . $fV . '" data-form-update-fragment="' . $fragmentNameEscaped . '"/>'; 895 896 $this->javaScriptInstructions['color'] ??= JavaScriptModuleInstruction::forRequireJS('TYPO3/CMS/Backend/ColorPicker') 897 ->invoke('initialize'); 898 break; 899 case 'wrap': 900 $wArr = explode('|', $fV); 901 $p_field = '<div class="input-group"> 902 <input class="form-control form-control-adapt" type="text" id="' . $idName . '" name="' . $fN . '" value="' . $wArr[0] . '" data-form-update-fragment="' . $fragmentNameEscaped . '" /> 903 <span class="input-group-addon input-group-icon">|</span> 904 <input class="form-control form-control-adapt" type="text" name="W' . $fN . '" value="' . $wArr[1] . '" data-form-update-fragment="' . $fragmentNameEscaped . '" /> 905 </div>'; 906 break; 907 case 'offset': 908 $wArr = explode(',', $fV); 909 $labels = GeneralUtility::trimExplode(',', $typeDat['paramstr']); 910 $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 . '" />'; 911 $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 . '" />'; 912 $labelsCount = count($labels); 913 for ($aa = 2; $aa < $labelsCount; $aa++) { 914 if ($labels[$aa]) { 915 $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 . '" />'; 916 } else { 917 $p_field .= '<input type="hidden" name="W' . $aa . $fN . '" value="' . $wArr[$aa] . '" />'; 918 } 919 } 920 $p_field = '<div class="input-group">' . $p_field . '</div>'; 921 break; 922 case 'options': 923 if (is_array($typeDat['params'])) { 924 $p_field = ''; 925 foreach ($typeDat['params'] as $val) { 926 $vParts = explode('=', $val, 2); 927 $label = $vParts[0]; 928 $val = $vParts[1] ?? $vParts[0]; 929 // option tag: 930 $sel = ''; 931 if ($val === $params['value']) { 932 $sel = ' selected'; 933 } 934 $p_field .= '<option value="' . htmlspecialchars($val) . '"' . $sel . '>' . $this->getLanguageService()->sL($label) . '</option>'; 935 } 936 $p_field = '<select class="form-select" id="' . $idName . '" name="' . $fN . '" data-form-update-fragment="' . $fragmentNameEscaped . '">' . $p_field . '</select>'; 937 } 938 break; 939 case 'boolean': 940 $sel = $fV ? 'checked' : ''; 941 $p_field = 942 '<input type="hidden" name="' . $fN . '" value="0" />' 943 . '<label class="btn btn-default btn-checkbox">' 944 . '<input id="' . $idName . '" type="checkbox" name="' . $fN . '" value="' . (($typeDat['paramstr'] ?? false) ?: 1) . '" ' . $sel . ' data-form-update-fragment="' . $fragmentNameEscaped . '" />' 945 . '<span class="t3-icon fa"></span>' 946 . '</label>'; 947 break; 948 case 'comment': 949 $sel = $fV ? '' : 'checked'; 950 $p_field = 951 '<input type="hidden" name="' . $fN . '" value="" />' 952 . '<label class="btn btn-default btn-checkbox">' 953 . '<input id="' . $idName . '" type="checkbox" name="' . $fN . '" value="1" ' . $sel . ' data-form-update-fragment="' . $fragmentNameEscaped . '" />' 954 . '<span class="t3-icon fa"></span>' 955 . '</label>'; 956 break; 957 case 'file': 958 // extensionlist 959 $extList = $typeDat['paramstr']; 960 if ($extList === 'IMAGE_EXT') { 961 $extList = $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']; 962 } 963 $p_field = '<option value="">(' . $extList . ')</option>'; 964 if (trim($params['value'])) { 965 $val = $params['value']; 966 $p_field .= '<option value=""></option>'; 967 $p_field .= '<option value="' . htmlspecialchars($val) . '" selected>' . $val . '</option>'; 968 } 969 $p_field = '<select class="form-select" id="' . $idName . '" name="' . $fN . '" data-form-update-fragment="' . $fragmentNameEscaped . '">' . $p_field . '</select>'; 970 break; 971 case 'user': 972 $userFunction = $typeDat['paramstr']; 973 $userFunctionParams = ['fieldName' => $fN, 'fieldValue' => $fV]; 974 $p_field = GeneralUtility::callUserFunction($userFunction, $userFunctionParams, $this); 975 break; 976 default: 977 $p_field = '<input class="form-control" id="' . $idName . '" type="text" name="' . $fN . '" value="' . $fV . '" data-form-update-fragment="' . $fragmentNameEscaped . '" />'; 978 } 979 // Define default names and IDs 980 $userTyposcriptID = 'userTS-' . $idName; 981 $defaultTyposcriptID = 'defaultTS-' . $idName; 982 $userTyposcriptStyle = ''; 983 // Set the default styling options 984 if (isset($this->objReg[$params['name']])) { 985 $checkboxValue = 'checked'; 986 $defaultTyposcriptStyle = 'style="display:none;"'; 987 } else { 988 $checkboxValue = ''; 989 $userTyposcriptStyle = 'style="display:none;"'; 990 $defaultTyposcriptStyle = ''; 991 } 992 $deleteIconHTML = 993 '<button type="button" class="btn btn-default t3js-toggle" data-bs-toggle="undo" rel="' . $idName . '">' 994 . '<span title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.deleteTitle')) . '">' 995 . $iconFactory->getIcon('actions-edit-undo', Icon::SIZE_SMALL)->render() 996 . '</span>' 997 . '</button>'; 998 $editIconHTML = 999 '<button type="button" class="btn btn-default t3js-toggle" data-bs-toggle="edit" rel="' . $idName . '">' 1000 . '<span title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.editTitle')) . '">' 1001 . $iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() 1002 . '</span>' 1003 . '</button>'; 1004 $constantCheckbox = '<input type="hidden" name="check[' . $params['name'] . ']" id="check-' . $idName . '" value="' . $checkboxValue . '"/>'; 1005 // If there's no default value for the field, use a static label. 1006 if (!$params['default_value']) { 1007 $params['default_value'] = '[Empty]'; 1008 } 1009 $constantDefaultRow = 1010 '<div class="input-group defaultTS" id="' . $defaultTyposcriptID . '" ' . $defaultTyposcriptStyle . '>' 1011 . '<span class="input-group-btn">' . $editIconHTML . '</span>' 1012 . '<input class="form-control" type="text" placeholder="' . htmlspecialchars($params['default_value']) . '" readonly>' 1013 . '</div>'; 1014 $constantEditRow = 1015 '<div class="input-group userTS" id="' . $userTyposcriptID . '" ' . $userTyposcriptStyle . '>' 1016 . '<span class="input-group-btn">' . $deleteIconHTML . '</span>' 1017 . $p_field 1018 . '</div>'; 1019 $constantData = 1020 $constantCheckbox 1021 . $constantEditRow 1022 . $constantDefaultRow; 1023 1024 $groupedOutput[$categoryLoop]['items'][] = [ 1025 'identifier' => $fragmentName, 1026 'label' => $head, 1027 'name' => $params['name'], 1028 'description' => $body, 1029 'hint' => $hint, 1030 'data' => $constantData, 1031 ]; 1032 } else { 1033 debug('Error. Constant did not exist. Should not happen.'); 1034 } 1035 } 1036 } 1037 return $groupedOutput; 1038 } 1039 1040 /*************************** 1041 * 1042 * Processing input values 1043 * 1044 ***************************/ 1045 /** 1046 * @param string $constants 1047 */ 1048 public function ext_regObjectPositions(string $constants): void 1049 { 1050 // This runs through the lines of the constants-field of the active template and registers the constants-names 1051 // and line positions in an array, $this->objReg 1052 $this->raw = explode(LF, $constants); 1053 $this->rawP = 0; 1054 // Resetting the objReg if the divider is found!! 1055 $this->objReg = []; 1056 $this->ext_regObjects(''); 1057 } 1058 1059 /** 1060 * @param string $pre 1061 */ 1062 public function ext_regObjects($pre) 1063 { 1064 // Works with regObjectPositions. "expands" the names of the TypoScript objects 1065 while (isset($this->raw[$this->rawP])) { 1066 $line = ltrim($this->raw[$this->rawP]); 1067 $this->rawP++; 1068 if ($line) { 1069 if ($line[0] === '[') { 1070 } elseif (strcspn($line, '}#/') != 0) { 1071 $varL = strcspn($line, ' {=<'); 1072 $var = substr($line, 0, $varL); 1073 $line = ltrim(substr($line, $varL)); 1074 switch ($line[0]) { 1075 case '=': 1076 $this->objReg[$pre . $var] = $this->rawP - 1; 1077 break; 1078 case '{': 1079 $this->ext_inBrace++; 1080 $this->ext_regObjects($pre . $var . '.'); 1081 break; 1082 } 1083 $this->lastComment = ''; 1084 } elseif ($line[0] === '}') { 1085 $this->lastComment = ''; 1086 $this->ext_inBrace--; 1087 if ($this->ext_inBrace < 0) { 1088 $this->ext_inBrace = 0; 1089 } else { 1090 break; 1091 } 1092 } 1093 } 1094 } 1095 } 1096 1097 /** 1098 * @param string $key 1099 * @param string $var 1100 */ 1101 public function ext_putValueInConf($key, $var) 1102 { 1103 // Puts the value $var to the TypoScript value $key in the current lines of the templates. 1104 // If the $key is not found in the template constants field, a new line is inserted in the bottom. 1105 $theValue = ' ' . trim($var); 1106 if (isset($this->objReg[$key])) { 1107 $lineNum = $this->objReg[$key]; 1108 $parts = explode('=', $this->raw[$lineNum], 2); 1109 if (count($parts) === 2) { 1110 $parts[1] = $theValue; 1111 } 1112 $this->raw[$lineNum] = implode('=', $parts); 1113 } else { 1114 $this->raw[] = $key . ' =' . $theValue; 1115 } 1116 $this->changed = true; 1117 } 1118 1119 /** 1120 * @param string $key 1121 */ 1122 public function ext_removeValueInConf($key) 1123 { 1124 // Removes the value in the configuration 1125 if (isset($this->objReg[$key])) { 1126 $lineNum = $this->objReg[$key]; 1127 unset($this->raw[$lineNum]); 1128 } 1129 $this->changed = true; 1130 } 1131 1132 /** 1133 * @param array $arr 1134 * @param array $settings 1135 * @return array 1136 */ 1137 public function ext_depthKeys($arr, $settings) 1138 { 1139 $tsbrArray = []; 1140 foreach ($arr as $theK => $theV) { 1141 $theKeyParts = explode('.', $theK); 1142 $depth = ''; 1143 $c = count($theKeyParts); 1144 $a = 0; 1145 foreach ($theKeyParts as $p) { 1146 $a++; 1147 $depth .= ($depth ? '.' : '') . $p; 1148 $tsbrArray[$depth] = $c == $a ? $theV : 1; 1149 } 1150 } 1151 // Modify settings 1152 foreach ($tsbrArray as $theK => $theV) { 1153 if ($theV) { 1154 $settings[$theK] = 1; 1155 } else { 1156 unset($settings[$theK]); 1157 } 1158 } 1159 return $settings; 1160 } 1161 1162 /** 1163 * Process input 1164 * 1165 * @param array $http_post_vars 1166 * @param array $http_post_files (not used anymore) 1167 * @param array $theConstants 1168 * @param array $tplRow Not used 1169 */ 1170 public function ext_procesInput($http_post_vars, $http_post_files, $theConstants, $tplRow) 1171 { 1172 $data = $http_post_vars['data'] ?? null; 1173 $check = $http_post_vars['check'] ?? []; 1174 $Wdata = $http_post_vars['Wdata'] ?? []; 1175 $W2data = $http_post_vars['W2data'] ?? []; 1176 $W3data = $http_post_vars['W3data'] ?? []; 1177 $W4data = $http_post_vars['W4data'] ?? []; 1178 $W5data = $http_post_vars['W5data'] ?? []; 1179 if (is_array($data)) { 1180 foreach ($data as $key => $var) { 1181 if (isset($theConstants[$key])) { 1182 // If checkbox is set, update the value 1183 if (isset($check[$key])) { 1184 // Exploding with linebreak, just to make sure that no multiline input is given! 1185 [$var] = explode(LF, $var); 1186 $typeDat = $this->ext_getTypeData($theConstants[$key]['type']); 1187 switch ($typeDat['type']) { 1188 case 'int': 1189 if ($typeDat['paramstr'] ?? false) { 1190 $var = MathUtility::forceIntegerInRange((int)$var, $typeDat['params'][0], $typeDat['params'][1]); 1191 } else { 1192 $var = (int)$var; 1193 } 1194 break; 1195 case 'int+': 1196 $var = max(0, (int)$var); 1197 break; 1198 case 'color': 1199 $col = []; 1200 if ($var) { 1201 $var = preg_replace('/[^A-Fa-f0-9]*/', '', $var) ?? ''; 1202 $useFulHex = strlen($var) > 3; 1203 $col[] = (int)hexdec($var[0]); 1204 $col[] = (int)hexdec($var[1]); 1205 $col[] = (int)hexdec($var[2]); 1206 if ($useFulHex) { 1207 $col[] = (int)hexdec($var[3]); 1208 $col[] = (int)hexdec($var[4]); 1209 $col[] = (int)hexdec($var[5]); 1210 } 1211 $var = substr('0' . dechex($col[0]), -1) . substr('0' . dechex($col[1]), -1) . substr('0' . dechex($col[2]), -1); 1212 if ($useFulHex) { 1213 $var .= substr('0' . dechex($col[3]), -1) . substr('0' . dechex($col[4]), -1) . substr('0' . dechex($col[5]), -1); 1214 } 1215 $var = '#' . strtoupper($var); 1216 } 1217 break; 1218 case 'comment': 1219 if ($var) { 1220 $var = ''; 1221 } else { 1222 $var = '#'; 1223 } 1224 break; 1225 case 'wrap': 1226 if (isset($Wdata[$key])) { 1227 $var .= '|' . $Wdata[$key]; 1228 } 1229 break; 1230 case 'offset': 1231 if (isset($Wdata[$key])) { 1232 $var = (int)$var . ',' . (int)$Wdata[$key]; 1233 if (isset($W2data[$key])) { 1234 $var .= ',' . (int)$W2data[$key]; 1235 if (isset($W3data[$key])) { 1236 $var .= ',' . (int)$W3data[$key]; 1237 if (isset($W4data[$key])) { 1238 $var .= ',' . (int)$W4data[$key]; 1239 if (isset($W5data[$key])) { 1240 $var .= ',' . (int)$W5data[$key]; 1241 } 1242 } 1243 } 1244 } 1245 } 1246 break; 1247 case 'boolean': 1248 if ($var) { 1249 $var = ($typeDat['paramstr'] ?? false) ?: 1; 1250 } 1251 break; 1252 } 1253 if ((string)($theConstants[$key]['value'] ?? '') !== (string)$var) { 1254 // Put value in, if changed. 1255 $this->ext_putValueInConf($key, $var); 1256 } 1257 // Remove the entry because it has been "used" 1258 unset($check[$key]); 1259 } else { 1260 $this->ext_removeValueInConf($key); 1261 } 1262 } 1263 } 1264 } 1265 // Remaining keys in $check indicates fields that are just clicked "on" to be edited. 1266 // Therefore we get the default value and puts that in the template as a start... 1267 foreach ($check ?? [] as $key => $var) { 1268 if (isset($theConstants[$key])) { 1269 $dValue = $theConstants[$key]['default_value']; 1270 $this->ext_putValueInConf($key, $dValue); 1271 } 1272 } 1273 } 1274 1275 /** 1276 * @param int $id 1277 * @param string $perms_clause 1278 * @return array 1279 */ 1280 public function ext_prevPageWithTemplate($id, $perms_clause) 1281 { 1282 $rootLine = BackendUtility::BEgetRootLine($id, $perms_clause ? ' AND ' . $perms_clause : ''); 1283 foreach ($rootLine as $p) { 1284 if ($this->ext_getFirstTemplate($p['uid'])) { 1285 return $p; 1286 } 1287 } 1288 return []; 1289 } 1290 1291 /** 1292 * Is set by runThroughTemplates(), previously set via TemplateAnalyzerModuleFunctionController from the outside 1293 * 1294 * @return array 1295 */ 1296 protected function getRootLine() 1297 { 1298 return is_array($this->absoluteRootLine) ? $this->absoluteRootLine : []; 1299 } 1300 1301 /** 1302 * @return LanguageService 1303 */ 1304 protected function getLanguageService() 1305 { 1306 return $GLOBALS['LANG']; 1307 } 1308} 1309