1<?php 2namespace TYPO3\CMS\Core\TypoScript; 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 17use TYPO3\CMS\Backend\Template\DocumentTemplate; 18use TYPO3\CMS\Backend\Utility\BackendUtility; 19use TYPO3\CMS\Core\Context\Context; 20use TYPO3\CMS\Core\Database\ConnectionPool; 21use TYPO3\CMS\Core\Database\Query\QueryBuilder; 22use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction; 23use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction; 24use TYPO3\CMS\Core\Exception; 25use TYPO3\CMS\Core\Imaging\Icon; 26use TYPO3\CMS\Core\Imaging\IconFactory; 27use TYPO3\CMS\Core\Localization\LanguageService; 28use TYPO3\CMS\Core\Utility\GeneralUtility; 29use TYPO3\CMS\Core\Utility\MathUtility; 30use TYPO3\CMS\Frontend\Configuration\TypoScript\ConditionMatching\ConditionMatcher; 31 32/** 33 * TSParser extension class to TemplateService 34 * Contains functions for the TS module in TYPO3 backend 35 * 36 * @internal this is only used for the TYPO3 TypoScript Template module, which should not be used in Extensions 37 */ 38class ExtendedTemplateService extends TemplateService 39{ 40 /** 41 * @var array 42 */ 43 public $categories = [ 44 'basic' => [], 45 // 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. 46 'menu' => [], 47 // Menu setup. This includes fontfiles, sizes, background images. Depending on the menutype. 48 'content' => [], 49 // All constants related to the display of pagecontent elements 50 'page' => [], 51 // General configuration like metatags, link targets 52 'advanced' => [], 53 // Advanced functions, which are used very seldom. 54 'all' => [] 55 ]; 56 57 /** 58 * Translated categories 59 * 60 * @var array 61 */ 62 protected $categoryLabels = []; 63 64 /** 65 * This will be filled with the available categories of the current template. 66 * 67 * @var array 68 */ 69 public $subCategories = [ 70 // Standard categories: 71 'enable' => ['Enable features', 'a'], 72 'dims' => ['Dimensions, widths, heights, pixels', 'b'], 73 'file' => ['Files', 'c'], 74 'typo' => ['Typography', 'd'], 75 'color' => ['Colors', 'e'], 76 'links' => ['Links and targets', 'f'], 77 'language' => ['Language specific constants', 'g'], 78 // subcategories based on the default content elements 79 'cheader' => ['Content: \'Header\'', 'ma'], 80 'cheader_g' => ['Content: \'Header\', Graphical', 'ma'], 81 'ctext' => ['Content: \'Text\'', 'mb'], 82 'cimage' => ['Content: \'Image\'', 'md'], 83 'ctextmedia' => ['Content: \'Textmedia\'', 'ml'], 84 'cbullets' => ['Content: \'Bullet list\'', 'me'], 85 'ctable' => ['Content: \'Table\'', 'mf'], 86 'cuploads' => ['Content: \'Filelinks\'', 'mg'], 87 'cmultimedia' => ['Content: \'Multimedia\'', 'mh'], 88 'cmedia' => ['Content: \'Media\'', 'mr'], 89 'cmailform' => ['Content: \'Form\'', 'mi'], 90 'csearch' => ['Content: \'Search\'', 'mj'], 91 'clogin' => ['Content: \'Login\'', 'mk'], 92 'cmenu' => ['Content: \'Menu/Sitemap\'', 'mm'], 93 'cshortcut' => ['Content: \'Insert records\'', 'mn'], 94 'clist' => ['Content: \'List of records\'', 'mo'], 95 'chtml' => ['Content: \'HTML\'', 'mq'] 96 ]; 97 98 /** 99 * Tsconstanteditor 100 * 101 * @var int 102 */ 103 public $ext_inBrace = 0; 104 105 /** 106 * Tsbrowser 107 * 108 * @var array 109 */ 110 public $tsbrowser_searchKeys = []; 111 112 /** 113 * @var array 114 */ 115 public $tsbrowser_depthKeys = []; 116 117 /** 118 * @var string 119 */ 120 public $constantMode = ''; 121 122 /** 123 * @var string 124 */ 125 public $regexMode = ''; 126 127 /** 128 * @var string 129 */ 130 public $fixedLgd = ''; 131 132 /** 133 * @var int 134 */ 135 public $ext_lineNumberOffset = 0; 136 137 /** 138 * @var int 139 */ 140 public $ext_expandAllNotes = 0; 141 142 /** 143 * @var int 144 */ 145 public $ext_noPMicons = 0; 146 147 /** 148 * @var array 149 */ 150 public $ext_listOfTemplatesArr = []; 151 152 /** 153 * @var string 154 */ 155 public $ext_lineNumberOffset_mode = ''; 156 157 /** 158 * Don't change 159 * 160 * @var int 161 */ 162 public $ext_dontCheckIssetValues = 0; 163 164 /** 165 * @var int 166 */ 167 public $ext_printAll = 0; 168 169 /** 170 * @var string 171 */ 172 public $ext_CEformName = 'forms[0]'; 173 174 /** 175 * @var bool 176 */ 177 public $doNotSortCategoriesBeforeMakingForm = false; 178 179 /** 180 * Ts analyzer 181 * 182 * @var array 183 */ 184 public $templateTitles = []; 185 186 /** 187 * @var array|null 188 */ 189 protected $lnToScript; 190 191 /** 192 * @var array 193 */ 194 public $clearList_const_temp; 195 196 /** 197 * @var array 198 */ 199 public $clearList_setup_temp; 200 201 /** 202 * @var string 203 */ 204 public $bType = ''; 205 206 /** 207 * @var bool 208 */ 209 public $linkObjects = false; 210 211 /** 212 * @var bool 213 */ 214 public $changed = false; 215 216 /** 217 * @var int[] 218 */ 219 protected $objReg = []; 220 221 /** 222 * @var array 223 */ 224 public $raw = []; 225 226 /** 227 * @var int 228 */ 229 public $rawP = 0; 230 231 /** 232 * @var string 233 */ 234 public $lastComment = ''; 235 236 /** 237 * @var array 238 */ 239 protected $inlineJavaScript = []; 240 241 /** 242 * @param Context|null $context 243 */ 244 public function __construct(Context $context = null) 245 { 246 parent::__construct($context); 247 // Disabled in backend context 248 $this->tt_track = false; 249 $this->verbose = false; 250 } 251 252 /** 253 * Gets the inline JavaScript. 254 * 255 * @return array 256 */ 257 public function getInlineJavaScript() 258 { 259 return $this->inlineJavaScript; 260 } 261 262 /** 263 * Substitute constant 264 * 265 * @param string $all 266 * @return string 267 */ 268 public function substituteConstants($all) 269 { 270 return preg_replace_callback('/\\{\\$(.[^}]+)\\}/', [$this, 'substituteConstantsCallBack'], $all); 271 } 272 273 /** 274 * Call back method for preg_replace_callback in substituteConstants 275 * 276 * @param array $matches Regular expression matches 277 * @return string Replacement 278 * @see substituteConstants() 279 */ 280 public function substituteConstantsCallBack($matches) 281 { 282 $marker = substr(md5($matches[0]), 0, 6); 283 switch ($this->constantMode) { 284 case 'const': 285 $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]; 286 break; 287 case 'subst': 288 $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]; 289 break; 290 case 'untouched': 291 $ret_val = $matches[0]; 292 break; 293 default: 294 $ret_val = isset($this->flatSetup[$matches[1]]) && !is_array($this->flatSetup[$matches[1]]) ? $this->flatSetup[$matches[1]] : $matches[0]; 295 } 296 return $ret_val; 297 } 298 299 /** 300 * Substitute markers added in substituteConstantsCallBack() 301 * with ##6chars_B##value1##6chars_M##value2##6chars_E## 302 * 303 * @param string $all 304 * @return string 305 */ 306 public function substituteCMarkers($all) 307 { 308 switch ($this->constantMode) { 309 case 'const': 310 case 'subst': 311 $all = preg_replace( 312 '/##[a-z0-9]{6}_B##(.*?)##[a-z0-9]{6}_M##(.*?)##[a-z0-9]{6}_E##/', 313 '<strong class="text-success" data-toggle="tooltip" data-placement="top" data-title="$1" title="$1">$2</strong>', 314 $all 315 ); 316 break; 317 default: 318 } 319 return $all; 320 } 321 322 /** 323 * Parse constants with respect to the constant-editor in this module. 324 * In particular comments in the code are registered and the edit_divider is taken into account. 325 * 326 * @return array 327 */ 328 public function generateConfig_constants() 329 { 330 // These vars are also set lateron... 331 $this->setup['sitetitle'] = $this->sitetitle; 332 // Parse constants 333 $constants = GeneralUtility::makeInstance(Parser\TypoScriptParser::class); 334 // Register comments! 335 $constants->regComments = true; 336 $constants->setup = $this->mergeConstantsFromPageTSconfig([]); 337 /** @var ConditionMatcher $matchObj */ 338 $matchObj = GeneralUtility::makeInstance(ConditionMatcher::class); 339 // Matches ALL conditions in TypoScript 340 $matchObj->setSimulateMatchResult(true); 341 $c = 0; 342 $cc = count($this->constants); 343 $defaultConstants = []; 344 foreach ($this->constants as $str) { 345 $c++; 346 if ($c == $cc) { 347 $this->flatSetup = []; 348 $this->flattenSetup($constants->setup, ''); 349 $defaultConstants = $this->flatSetup; 350 } 351 $constants->parse($str, $matchObj); 352 } 353 $this->flatSetup = []; 354 $this->flattenSetup($constants->setup, ''); 355 $this->setup['constants'] = $constants->setup; 356 return $this->ext_compareFlatSetups($defaultConstants); 357 } 358 359 /** 360 * @param array $theSetup 361 * @param string $theKey 362 * @return array 363 */ 364 public function ext_getSetup($theSetup, $theKey) 365 { 366 $parts = explode('.', $theKey, 2); 367 if ((string)$parts[0] !== '' && is_array($theSetup[$parts[0] . '.'])) { 368 if (trim($parts[1]) !== '') { 369 return $this->ext_getSetup($theSetup[$parts[0] . '.'], trim($parts[1])); 370 } 371 return [$theSetup[$parts[0] . '.'], $theSetup[$parts[0]]]; 372 } 373 if (trim($theKey) !== '') { 374 return [[], $theSetup[$theKey]]; 375 } 376 return [$theSetup, '']; 377 } 378 379 /** 380 * Get object tree 381 * 382 * @param array $arr 383 * @param string $depth_in 384 * @param string $depthData 385 * @param string $parentType (unused) 386 * @param string $parentValue (unused) 387 * @param string $alphaSort sorts the array keys / tree by alphabet when set to 1 388 * @return string 389 */ 390 public function ext_getObjTree($arr, $depth_in, $depthData, $parentType = '', $parentValue = '', $alphaSort = '0') 391 { 392 $HTML = ''; 393 if ($alphaSort == '1') { 394 ksort($arr); 395 } 396 $keyArr_num = []; 397 $keyArr_alpha = []; 398 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */ 399 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class); 400 foreach ($arr as $key => $value) { 401 // Don't do anything with comments / linenumber registrations... 402 if (substr($key, -2) !== '..') { 403 $key = preg_replace('/\\.$/', '', $key); 404 if (substr($key, -1) !== '.') { 405 if (MathUtility::canBeInterpretedAsInteger($key)) { 406 $keyArr_num[$key] = $arr[$key]; 407 } else { 408 $keyArr_alpha[$key] = $arr[$key]; 409 } 410 } 411 } 412 } 413 ksort($keyArr_num); 414 $keyArr = $keyArr_num + $keyArr_alpha; 415 if ($depth_in) { 416 $depth_in = $depth_in . '.'; 417 } 418 foreach ($keyArr as $key => $value) { 419 $depth = $depth_in . $key; 420 // This excludes all constants starting with '_' from being shown. 421 if ($this->bType !== 'const' || $depth[0] !== '_') { 422 $goto = substr(md5($depth), 0, 6); 423 $deeper = is_array($arr[$key . '.']) && ($this->tsbrowser_depthKeys[$depth] || $this->ext_expandAllNotes); 424 $PM = is_array($arr[$key . '.']) && !$this->ext_noPMicons ? ($deeper ? 'minus' : 'plus') : 'join'; 425 $HTML .= $depthData . '<li><span class="list-tree-group">'; 426 if ($PM !== 'join') { 427 $urlParameters = [ 428 'id' => (int)GeneralUtility::_GP('id'), 429 'tsbr[' . $depth . ']' => $deeper ? 0 : 1 430 ]; 431 if (GeneralUtility::_GP('breakPointLN')) { 432 $urlParameters['breakPointLN'] = GeneralUtility::_GP('breakPointLN'); 433 } 434 $aHref = (string)$uriBuilder->buildUriFromRoute('web_ts', $urlParameters) . '#' . $goto; 435 $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>'; 436 } 437 $label = $key; 438 // Read only... 439 if (($depth === 'types' || $depth === 'resources' || $depth === 'sitetitle') && $this->bType === 'setup') { 440 $label = '<span style="color: #666666;">' . $label . '</span>'; 441 } else { 442 if ($this->linkObjects) { 443 $urlParameters = [ 444 'id' => (int)GeneralUtility::_GP('id'), 445 'sObj' => $depth 446 ]; 447 if (GeneralUtility::_GP('breakPointLN')) { 448 $urlParameters['breakPointLN'] = GeneralUtility::_GP('breakPointLN'); 449 } 450 $aHref = (string)$uriBuilder->buildUriFromRoute('web_ts', $urlParameters); 451 if ($this->bType !== 'const') { 452 $ln = is_array($arr[$key . '.ln..']) ? 'Defined in: ' . $this->lineNumberToScript($arr[$key . '.ln..']) : 'N/A'; 453 } else { 454 $ln = ''; 455 } 456 if ($this->tsbrowser_searchKeys[$depth] & 4) { 457 // The key has matched the search string 458 $label = '<strong class="text-danger">' . $label . '</strong>'; 459 } 460 $label = '<a href="' . htmlspecialchars($aHref) . '" title="' . htmlspecialchars($depth_in . $key . ' ' . $ln) . '">' . $label . '</a>'; 461 } 462 } 463 $HTML .= '<span class="list-tree-label" title="' . htmlspecialchars($depth_in . $key) . '">[' . $label . ']</span>'; 464 if (isset($arr[$key])) { 465 $theValue = $arr[$key]; 466 if ($this->fixedLgd) { 467 $imgBlocks = ceil(1 + strlen($depthData) / 77); 468 $lgdChars = 68 - ceil(strlen('[' . $key . ']') * 0.8) - $imgBlocks * 3; 469 $theValue = $this->ext_fixed_lgd($theValue, $lgdChars); 470 } 471 // The value has matched the search string 472 if ($this->tsbrowser_searchKeys[$depth] & 2) { 473 $HTML .= ' = <span class="list-tree-value text-danger">' . htmlspecialchars($theValue) . '</span>'; 474 } else { 475 $HTML .= ' = <span class="list-tree-value">' . htmlspecialchars($theValue) . '</span>'; 476 } 477 if ($this->ext_regComments && isset($arr[$key . '..'])) { 478 $comment = $arr[$key . '..']; 479 // Skip INCLUDE_TYPOSCRIPT comments, they are almost useless 480 if (!preg_match('/### <INCLUDE_TYPOSCRIPT:.*/', $comment)) { 481 // Remove linebreaks, replace with ' ' 482 $comment = preg_replace('/[\\r\\n]/', ' ', $comment); 483 // Remove # and * if more than twice in a row 484 $comment = preg_replace('/[#\\*]{2,}/', '', $comment); 485 // Replace leading # (just if it exists) and add it again. Result: Every comment should be prefixed by a '#'. 486 $comment = preg_replace('/^[#\\*\\s]+/', '# ', $comment); 487 // Masking HTML Tags: Replace < with < and > with > 488 $comment = htmlspecialchars($comment); 489 $HTML .= ' <i class="text-muted">' . trim($comment) . '</i>'; 490 } 491 } 492 } 493 $HTML .= '</span>'; 494 if ($deeper) { 495 $HTML .= $this->ext_getObjTree($arr[$key . '.'], $depth, $depthData, '', $arr[$key], $alphaSort); 496 } 497 } 498 } 499 if ($HTML !== '') { 500 $HTML = '<ul class="list-tree text-monospace">' . $HTML . '</ul>'; 501 } 502 503 return $HTML; 504 } 505 506 /** 507 * Find the originating template name for an array of line numbers (TypoScript setup only!) 508 * Given an array of linenumbers the method will try to find the corresponding template where this line originated 509 * The linenumber indicates the *last* lineNumber that is part of the template 510 * 511 * lineNumbers are in sync with the calculated lineNumbers '.ln..' in TypoScriptParser 512 * 513 * @param array $lnArr Array with linenumbers (might have some extra symbols, for example for unsetting) to be processed 514 * @return array The same array where each entry has been prepended by the template title if available 515 */ 516 public function lineNumberToScript(array $lnArr) 517 { 518 // On the first call, construct the lnToScript array. 519 if (!is_array($this->lnToScript)) { 520 $this->lnToScript = []; 521 522 // aggregatedTotalLineCount 523 $c = 0; 524 foreach ($this->hierarchyInfo as $templateNumber => $info) { 525 // hierarchyInfo has the number of lines in configLines, but unfortunately this value 526 // was calculated *before* processing of any INCLUDE instructions 527 // for some yet unknown reason we have to add an extra +2 offset 528 $linecountAfterIncludeProcessing = substr_count($this->config[$templateNumber], LF) + 2; 529 $c += $linecountAfterIncludeProcessing; 530 $this->lnToScript[$c] = $info['title']; 531 } 532 } 533 534 foreach ($lnArr as $k => $ln) { 535 foreach ($this->lnToScript as $endLn => $title) { 536 if ($endLn >= (int)$ln) { 537 $lnArr[$k] = '"' . $title . '", ' . $ln; 538 break; 539 } 540 } 541 } 542 543 return implode('; ', $lnArr); 544 } 545 546 /** 547 * @param array $arr 548 * @param string $depth_in 549 * @param string $searchString 550 * @param array $keyArray 551 * @return array 552 * @throws Exception 553 */ 554 public function ext_getSearchKeys($arr, $depth_in, $searchString, $keyArray) 555 { 556 $keyArr = []; 557 foreach ($arr as $key => $value) { 558 $key = preg_replace('/\\.$/', '', $key); 559 if (substr($key, -1) !== '.') { 560 $keyArr[$key] = 1; 561 } 562 } 563 if ($depth_in) { 564 $depth_in = $depth_in . '.'; 565 } 566 $searchPattern = ''; 567 if ($this->regexMode) { 568 $searchPattern = '/' . addcslashes($searchString, '/') . '/'; 569 $matchResult = @preg_match($searchPattern, ''); 570 if ($matchResult === false) { 571 throw new Exception(sprintf('Error evaluating regular expression "%s".', $searchPattern), 1446559458); 572 } 573 } 574 foreach ($keyArr as $key => $value) { 575 $depth = $depth_in . $key; 576 $deeper = is_array($arr[$key . '.']); 577 if ($this->regexMode) { 578 // The value has matched 579 if (preg_match($searchPattern, $arr[$key])) { 580 $this->tsbrowser_searchKeys[$depth] += 2; 581 } 582 // The key has matched 583 if (preg_match($searchPattern, $key)) { 584 $this->tsbrowser_searchKeys[$depth] += 4; 585 } 586 // Just open this subtree if the parent key has matched the search 587 if (preg_match($searchPattern, $depth_in)) { 588 $this->tsbrowser_searchKeys[$depth] = 1; 589 } 590 } else { 591 // The value has matched 592 if (stristr($arr[$key], $searchString)) { 593 $this->tsbrowser_searchKeys[$depth] += 2; 594 } 595 // The key has matches 596 if (stristr($key, $searchString)) { 597 $this->tsbrowser_searchKeys[$depth] += 4; 598 } 599 // Just open this subtree if the parent key has matched the search 600 if (stristr($depth_in, $searchString)) { 601 $this->tsbrowser_searchKeys[$depth] = 1; 602 } 603 } 604 if ($deeper) { 605 $cS = count($this->tsbrowser_searchKeys); 606 $keyArray = $this->ext_getSearchKeys($arr[$key . '.'], $depth, $searchString, $keyArray); 607 if ($cS != count($this->tsbrowser_searchKeys)) { 608 $keyArray[$depth] = 1; 609 } 610 } 611 } 612 return $keyArray; 613 } 614 615 /** 616 * @param int $pid 617 * @return int 618 */ 619 public function ext_getRootlineNumber($pid) 620 { 621 if ($pid) { 622 foreach ($this->getRootLine() as $key => $val) { 623 if ((int)$val['uid'] === (int)$pid) { 624 return (int)$key; 625 } 626 } 627 } 628 return -1; 629 } 630 631 /** 632 * @param array $arr 633 * @param string $depthData 634 * @param array $keyArray 635 * @param int $first 636 * @return array 637 */ 638 public function ext_getTemplateHierarchyArr($arr, $depthData, $keyArray, $first = 0) 639 { 640 $keyArr = []; 641 foreach ($arr as $key => $value) { 642 $key = preg_replace('/\\.$/', '', $key); 643 if (substr($key, -1) !== '.') { 644 $keyArr[$key] = 1; 645 } 646 } 647 $a = 0; 648 $c = count($keyArr); 649 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */ 650 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class); 651 /** @var IconFactory $iconFactory */ 652 $iconFactory = GeneralUtility::makeInstance(IconFactory::class); 653 foreach ($keyArr as $key => $value) { 654 $HTML = ''; 655 $a++; 656 $deeper = is_array($arr[$key . '.']); 657 $row = $arr[$key]; 658 $LN = $a == $c ? 'blank' : 'line'; 659 $BTM = $a == $c ? 'top' : ''; 660 $HTML .= $depthData; 661 $alttext = '[' . $row['templateID'] . ']'; 662 $alttext .= $row['pid'] ? ' - ' . BackendUtility::getRecordPath($row['pid'], '1=1', 20) : ''; 663 $icon = strpos($row['templateID'], 'sys') === 0 664 ? '<span title="' . htmlspecialchars($alttext) . '">' . $iconFactory->getIconForRecord('sys_template', $row, Icon::SIZE_SMALL)->render() . '</span>' 665 : '<span title="' . htmlspecialchars($alttext) . '">' . $iconFactory->getIcon('mimetypes-x-content-template-static', Icon::SIZE_SMALL)->render() . '</span>'; 666 if (in_array($row['templateID'], $this->clearList_const) || in_array($row['templateID'], $this->clearList_setup)) { 667 $urlParameters = [ 668 'id' => (int)GeneralUtility::_GP('id'), 669 'template' => $row['templateID'] 670 ]; 671 $aHref = (string)$uriBuilder->buildUriFromRoute('web_ts', $urlParameters); 672 $A_B = '<a href="' . htmlspecialchars($aHref) . '">'; 673 $A_E = '</a>'; 674 if (GeneralUtility::_GP('template') == $row['templateID']) { 675 $A_B = '<strong>' . $A_B; 676 $A_E .= '</strong>'; 677 } 678 } else { 679 $A_B = ''; 680 $A_E = ''; 681 } 682 $HTML .= ($first ? '' : '<span class="treeline-icon treeline-icon-join' . $BTM . '"></span>') . $icon . ' ' . $A_B 683 . htmlspecialchars(GeneralUtility::fixed_lgd_cs($row['title'], $GLOBALS['BE_USER']->uc['titleLen'])) 684 . $A_E . ' '; 685 $RL = $this->ext_getRootlineNumber($row['pid']); 686 $statusCheckedIcon = $iconFactory->getIcon('status-status-checked', Icon::SIZE_SMALL)->render(); 687 $keyArray[] = '<tr> 688 <td class="nowrap">' . $HTML . '</td> 689 <td align="center">' . ($row['root'] ? $statusCheckedIcon : '') . '</td> 690 <td align="center">' . ($row['clConf'] ? $statusCheckedIcon : '') . '</td> 691 <td align="center">' . ($row['clConst'] ? $statusCheckedIcon : '') . '</td> 692 <td align="center">' . ($row['pid'] ?: '') . '</td> 693 <td align="center">' . ($RL >= 0 ? $RL : '') . '</td> 694 <td>' . ($row['next'] ? $row['next'] : '') . '</td> 695 </tr>'; 696 if ($deeper) { 697 $keyArray = $this->ext_getTemplateHierarchyArr($arr[$key . '.'], $depthData . ($first ? '' : '<span class="treeline-icon treeline-icon-' . $LN . '"></span>'), $keyArray); 698 } 699 } 700 return $keyArray; 701 } 702 703 /** 704 * Processes the flat array from TemplateService->hierarchyInfo 705 * and turns it into a hierarchical array to show dependencies (used by TemplateAnalyzer) 706 * 707 * @param array $depthDataArr (empty array on external call) 708 * @param int &$pointer Element number (1! to count()) of $this->hierarchyInfo that should be processed. 709 * @return array Processed hierachyInfo. 710 */ 711 public function ext_process_hierarchyInfo(array $depthDataArr, &$pointer) 712 { 713 $parent = $this->hierarchyInfo[$pointer - 1]['templateParent']; 714 while ($pointer > 0 && $this->hierarchyInfo[$pointer - 1]['templateParent'] == $parent) { 715 $pointer--; 716 $row = $this->hierarchyInfo[$pointer]; 717 $depthDataArr[$row['templateID']] = $row; 718 unset($this->clearList_setup_temp[$row['templateID']]); 719 unset($this->clearList_const_temp[$row['templateID']]); 720 $this->templateTitles[$row['templateID']] = $row['title']; 721 if ($row['templateID'] == $this->hierarchyInfo[$pointer - 1]['templateParent']) { 722 $depthDataArr[$row['templateID'] . '.'] = $this->ext_process_hierarchyInfo([], $pointer); 723 } 724 } 725 return $depthDataArr; 726 } 727 728 /** 729 * Get formatted HTML output for TypoScript either with Syntaxhiglighting or in plain mode 730 * 731 * @param array $config Array with simple strings of typoscript code. 732 * @param bool $lineNumbers Prepend linNumbers to each line. 733 * @param bool $comments Enable including comments in output. 734 * @param bool $crop Enable cropping of long lines. 735 * @param bool $syntaxHL Enrich output with syntaxhighlighting. 736 * @param int $syntaxHLBlockmode 737 * @return string 738 */ 739 public function ext_outputTS( 740 array $config, 741 $lineNumbers = false, 742 $comments = false, 743 $crop = false, 744 $syntaxHL = false, 745 $syntaxHLBlockmode = 0 746 ) { 747 $all = ''; 748 foreach ($config as $str) { 749 $all .= '[GLOBAL]' . LF . $str; 750 } 751 if ($syntaxHL) { 752 $tsparser = GeneralUtility::makeInstance(Parser\TypoScriptParser::class); 753 $tsparser->lineNumberOffset = $this->ext_lineNumberOffset + 1; 754 $tsparser->parentObject = $this; 755 return $tsparser->doSyntaxHighlight($all, $lineNumbers ? [$this->ext_lineNumberOffset + 1] : '', $syntaxHLBlockmode); 756 } 757 return $this->ext_formatTS($all, $lineNumbers, $comments, $crop); 758 } 759 760 /** 761 * Returns a new string of max. $chars length 762 * If the string is longer, it will be truncated and prepended with '...' 763 * $chars must be an integer of at least 4 764 * 765 * @param string $string 766 * @param int $chars 767 * @return string 768 */ 769 public function ext_fixed_lgd($string, $chars) 770 { 771 if ($chars >= 4) { 772 if (strlen($string) > $chars) { 773 if (strlen($string) > 24 && preg_match('/^##[a-z0-9]{6}_B##$/', substr($string, 0, 12))) { 774 $string = GeneralUtility::fixed_lgd_cs(substr($string, 12, -12), $chars - 3); 775 $marker = substr(md5($string), 0, 6); 776 return '##' . $marker . '_B##' . $string . '##' . $marker . '_E##'; 777 } 778 return GeneralUtility::fixed_lgd_cs($string, $chars - 3); 779 } 780 } 781 return $string; 782 } 783 784 /** 785 * @param int $lineNumber Line Number 786 * @param array $str 787 * @return string 788 */ 789 public function ext_lnBreakPointWrap($lineNumber, $str) 790 { 791 return '<a href="#" id="line-' . $lineNumber . '" onClick="return brPoint(' . $lineNumber . ',' 792 . ($this->ext_lineNumberOffset_mode === 'setup' ? 1 : 0) . ');">' . $str . '</a>'; 793 } 794 795 /** 796 * @param string $input 797 * @param bool $ln 798 * @param bool $comments 799 * @param bool $crop 800 * @return string 801 */ 802 public function ext_formatTS($input, $ln, $comments = true, $crop = false) 803 { 804 $cArr = explode(LF, $input); 805 $n = ceil(log10(count($cArr) + $this->ext_lineNumberOffset)); 806 $lineNum = ''; 807 foreach ($cArr as $k => $v) { 808 $lln = $k + $this->ext_lineNumberOffset + 1; 809 if ($ln) { 810 $lineNum = $this->ext_lnBreakPointWrap($lln, str_replace(' ', ' ', sprintf('% ' . $n . 'd', $lln))) . ': '; 811 } 812 $v = htmlspecialchars($v); 813 if ($crop) { 814 $v = $this->ext_fixed_lgd($v, $ln ? 71 : 77); 815 } 816 $cArr[$k] = $lineNum . str_replace(' ', ' ', $v); 817 $firstChar = substr(trim($v), 0, 1); 818 if ($firstChar === '[') { 819 $cArr[$k] = '<strong class="text-success">' . $cArr[$k] . '</strong>'; 820 } elseif ($firstChar === '/' || $firstChar === '#') { 821 if ($comments) { 822 $cArr[$k] = '<span class="text-muted">' . $cArr[$k] . '</span>'; 823 } else { 824 unset($cArr[$k]); 825 } 826 } 827 } 828 $output = implode('<br />', $cArr) . '<br />'; 829 return $output; 830 } 831 832 /** 833 * Get a single sys_template record attached to a single page. 834 * If multiple template records are on this page, the first (order by sorting) 835 * record will be returned, unless a specific template uid is specified via $templateUid 836 * 837 * @param int $pid The pid to select sys_template records from 838 * @param int $templateUid Optional template uid 839 * @return array|null Returns the template record or null if none was found 840 */ 841 public function ext_getFirstTemplate($pid, $templateUid = 0) 842 { 843 if (empty($pid)) { 844 return null; 845 } 846 847 // Query is taken from the runThroughTemplates($theRootLine) function in the parent class. 848 $queryBuilder = $this->getTemplateQueryBuilder($pid) 849 ->setMaxResults(1); 850 if ($templateUid) { 851 $queryBuilder->andWhere( 852 $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($templateUid, \PDO::PARAM_INT)) 853 ); 854 } 855 $row = $queryBuilder->execute()->fetch(); 856 BackendUtility::workspaceOL('sys_template', $row); 857 858 return $row; 859 } 860 861 /** 862 * Get an array of all template records on a page. 863 * 864 * @param int $pid Pid to fetch sys_template records for 865 * @return array[] Array of template records 866 */ 867 public function ext_getAllTemplates($pid): array 868 { 869 if (empty($pid)) { 870 return []; 871 } 872 $result = $this->getTemplateQueryBuilder($pid)->execute(); 873 $outRes = []; 874 while ($row = $result->fetch()) { 875 BackendUtility::workspaceOL('sys_template', $row); 876 if (is_array($row)) { 877 $outRes[] = $row; 878 } 879 } 880 return $outRes; 881 } 882 883 /** 884 * Internal helper method to prepare the query builder for 885 * getting sys_template records from a given pid 886 * 887 * @param int $pid The pid to select sys_template records from 888 * @return QueryBuilder Returns a QueryBuilder 889 */ 890 protected function getTemplateQueryBuilder(int $pid): QueryBuilder 891 { 892 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) 893 ->getQueryBuilderForTable('sys_template'); 894 $queryBuilder->getRestrictions() 895 ->removeAll() 896 ->add(GeneralUtility::makeInstance(DeletedRestriction::class)) 897 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class)); 898 899 $queryBuilder->select('*') 900 ->from('sys_template') 901 ->where( 902 $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)) 903 ); 904 if (!empty($GLOBALS['TCA']['sys_template']['ctrl']['sortby'])) { 905 $queryBuilder->orderBy($GLOBALS['TCA']['sys_template']['ctrl']['sortby']); 906 } 907 908 return $queryBuilder; 909 } 910 911 /** 912 * This function compares the flattened constants (default and all). 913 * Returns an array with the constants from the whole template which may be edited by the module. 914 * 915 * @param array $default 916 * @return array 917 */ 918 public function ext_compareFlatSetups($default) 919 { 920 $editableComments = []; 921 $counter = 0; 922 foreach ($this->flatSetup as $const => $value) { 923 if (substr($const, -2) === '..' || !isset($this->flatSetup[$const . '..'])) { 924 continue; 925 } 926 $counter++; 927 $comment = trim($this->flatSetup[$const . '..']); 928 $c_arr = explode(LF, $comment); 929 foreach ($c_arr as $k => $v) { 930 $line = trim(preg_replace('/^[#\\/]*/', '', $v)); 931 if (!$line) { 932 continue; 933 } 934 $parts = explode(';', $line); 935 foreach ($parts as $par) { 936 if (strstr($par, '=')) { 937 $keyValPair = explode('=', $par, 2); 938 switch (trim(strtolower($keyValPair[0]))) { 939 case 'type': 940 // Type: 941 $editableComments[$const]['type'] = trim($keyValPair[1]); 942 break; 943 case 'cat': 944 // List of categories. 945 $catSplit = explode('/', strtolower($keyValPair[1])); 946 $catSplit[0] = trim($catSplit[0]); 947 if (isset($this->categoryLabels[$catSplit[0]])) { 948 $catSplit[0] = $this->categoryLabels[$catSplit[0]]; 949 } 950 $editableComments[$const]['cat'] = $catSplit[0]; 951 // This is the subcategory. Must be a key in $this->subCategories[]. 952 // catSplit[2] represents the search-order within the subcat. 953 $catSplit[1] = trim($catSplit[1]); 954 if ($catSplit[1] && isset($this->subCategories[$catSplit[1]])) { 955 $editableComments[$const]['subcat_name'] = $catSplit[1]; 956 $orderIdentifier = isset($catSplit[2]) ? trim($catSplit[2]) : $counter; 957 $editableComments[$const]['subcat'] = $this->subCategories[$catSplit[1]][1] 958 . '/' . $catSplit[1] . '/' . $orderIdentifier . 'z'; 959 } elseif (isset($catSplit[2])) { 960 $editableComments[$const]['subcat'] = 'x' . '/' . trim($catSplit[2]) . 'z'; 961 } else { 962 $editableComments[$const]['subcat'] = 'x' . '/' . $counter . 'z'; 963 } 964 break; 965 case 'label': 966 // Label 967 $editableComments[$const]['label'] = trim($keyValPair[1]); 968 break; 969 case 'customcategory': 970 // Custom category label 971 $customCategory = explode('=', $keyValPair[1], 2); 972 if (trim($customCategory[0])) { 973 $categoryKey = strtolower($customCategory[0]); 974 $this->categoryLabels[$categoryKey] = $this->getLanguageService()->sL($customCategory[1]); 975 } 976 break; 977 case 'customsubcategory': 978 // Custom subCategory label 979 $customSubcategory = explode('=', $keyValPair[1], 2); 980 if (trim($customSubcategory[0])) { 981 $subCategoryKey = strtolower($customSubcategory[0]); 982 $this->subCategories[$subCategoryKey][0] = $this->getLanguageService()->sL($customSubcategory[1]); 983 } 984 break; 985 } 986 } 987 } 988 } 989 if (isset($editableComments[$const])) { 990 $editableComments[$const]['name'] = $const; 991 $editableComments[$const]['value'] = trim($value); 992 if (isset($default[$const])) { 993 $editableComments[$const]['default_value'] = trim($default[$const]); 994 } 995 } 996 } 997 return $editableComments; 998 } 999 1000 /** 1001 * @param array $editConstArray 1002 */ 1003 public function ext_categorizeEditableConstants($editConstArray) 1004 { 1005 // Runs through the available constants and fills the $this->categories array with pointers and priority-info 1006 foreach ($editConstArray as $constName => $constData) { 1007 if (!$constData['type']) { 1008 $constData['type'] = 'string'; 1009 } 1010 $cats = explode(',', $constData['cat']); 1011 // if = only one category, while allows for many. We have agreed on only one category is the most basic way... 1012 foreach ($cats as $theCat) { 1013 $theCat = trim($theCat); 1014 if ($theCat) { 1015 $this->categories[$theCat][$constName] = $constData['subcat']; 1016 } 1017 } 1018 } 1019 } 1020 1021 /** 1022 * @return array 1023 */ 1024 public function ext_getCategoryLabelArray() 1025 { 1026 // Returns array used for labels in the menu. 1027 $retArr = []; 1028 foreach ($this->categories as $k => $v) { 1029 if (!empty($v)) { 1030 $retArr[$k] = strtoupper($k) . ' (' . count($v) . ')'; 1031 } 1032 } 1033 return $retArr; 1034 } 1035 1036 /** 1037 * @param string $type 1038 * @return array 1039 */ 1040 public function ext_getTypeData($type) 1041 { 1042 $retArr = []; 1043 $type = trim($type); 1044 if (!$type) { 1045 $retArr['type'] = 'string'; 1046 } else { 1047 $m = strcspn($type, ' ['); 1048 $retArr['type'] = strtolower(substr($type, 0, $m)); 1049 $types = ['int' => 1, 'options' => 1, 'file' => 1, 'boolean' => 1, 'offset' => 1, 'user' => 1]; 1050 if (isset($types[$retArr['type']])) { 1051 $p = trim(substr($type, $m)); 1052 $reg = []; 1053 preg_match('/\\[(.*)\\]/', $p, $reg); 1054 $p = trim($reg[1]); 1055 if ($p) { 1056 $retArr['paramstr'] = $p; 1057 switch ($retArr['type']) { 1058 case 'int': 1059 if ($retArr['paramstr'][0] === '-') { 1060 $retArr['params'] = GeneralUtility::intExplode('-', substr($retArr['paramstr'], 1)); 1061 $retArr['params'][0] = (int)('-' . $retArr['params'][0]); 1062 } else { 1063 $retArr['params'] = GeneralUtility::intExplode('-', $retArr['paramstr']); 1064 } 1065 $retArr['min'] = $retArr['params'][0]; 1066 $retArr['max'] = $retArr['params'][1]; 1067 $retArr['paramstr'] = $retArr['params'][0] . ' - ' . $retArr['params'][1]; 1068 break; 1069 case 'options': 1070 $retArr['params'] = explode(',', $retArr['paramstr']); 1071 break; 1072 } 1073 } 1074 } 1075 } 1076 return $retArr; 1077 } 1078 1079 /** 1080 * @param array $params 1081 * @return array 1082 */ 1083 public function ext_fNandV($params) 1084 { 1085 $fN = 'data[' . $params['name'] . ']'; 1086 $idName = str_replace('.', '-', $params['name']); 1087 $fV = $params['value']; 1088 // Values entered from the constantsedit cannot be constants! 230502; removed \{ and set { 1089 if (preg_match('/^{[\\$][a-zA-Z0-9\\.]*}$/', trim($fV), $reg)) { 1090 $fV = ''; 1091 } 1092 $fV = htmlspecialchars($fV); 1093 return [$fN, $fV, $params, $idName]; 1094 } 1095 1096 /** 1097 * This functions returns the HTML-code that creates the editor-layout of the module. 1098 * 1099 * @param array $theConstants 1100 * @param string $category 1101 * @return string 1102 */ 1103 public function ext_printFields($theConstants, $category) 1104 { 1105 reset($theConstants); 1106 $output = ''; 1107 $subcat = ''; 1108 if (is_array($this->categories[$category])) { 1109 if (!$this->doNotSortCategoriesBeforeMakingForm) { 1110 asort($this->categories[$category]); 1111 } 1112 /** @var IconFactory $iconFactory */ 1113 $iconFactory = GeneralUtility::makeInstance(IconFactory::class); 1114 foreach ($this->categories[$category] as $name => $type) { 1115 $params = $theConstants[$name]; 1116 if (is_array($params)) { 1117 if ($subcat != $params['subcat_name']) { 1118 $subcat = $params['subcat_name']; 1119 $subcat_name = $params['subcat_name'] ? $this->subCategories[$params['subcat_name']][0] : 'Others'; 1120 $output .= '<h3>' . $subcat_name . '</h3>'; 1121 } 1122 $label = $this->getLanguageService()->sL($params['label']); 1123 $label_parts = explode(':', $label, 2); 1124 if (count($label_parts) === 2) { 1125 $head = trim($label_parts[0]); 1126 $body = trim($label_parts[1]); 1127 } else { 1128 $head = trim($label_parts[0]); 1129 $body = ''; 1130 } 1131 $typeDat = $this->ext_getTypeData($params['type']); 1132 $p_field = ''; 1133 $raname = substr(md5($params['name']), 0, 10); 1134 $aname = '\'' . $raname . '\''; 1135 list($fN, $fV, $params, $idName) = $this->ext_fNandV($params); 1136 $idName = htmlspecialchars($idName); 1137 $hint = ''; 1138 switch ($typeDat['type']) { 1139 case 'int': 1140 case 'int+': 1141 $additionalAttributes = ''; 1142 if ($typeDat['paramstr']) { 1143 $hint = ' Range: ' . $typeDat['paramstr']; 1144 } elseif ($typeDat['type'] === 'int+') { 1145 $hint = ' Range: 0 - '; 1146 $typeDat['min'] = 0; 1147 } else { 1148 $hint = ' (Integer)'; 1149 } 1150 1151 if (isset($typeDat['min'])) { 1152 $additionalAttributes .= ' min="' . (int)$typeDat['min'] . '" '; 1153 } 1154 if (isset($typeDat['max'])) { 1155 $additionalAttributes .= ' max="' . (int)$typeDat['max'] . '" '; 1156 } 1157 1158 $p_field = 1159 '<input class="form-control" id="' . $idName . '" type="number"' 1160 . ' name="' . $fN . '" value="' . $fV . '"' . ' onChange="uFormUrl(' . $aname . ')"' . $additionalAttributes . ' />'; 1161 break; 1162 case 'color': 1163 $p_field = ' 1164 <input class="form-control formengine-colorpickerelement t3js-color-picker" type="text" id="input-' . $idName . '" rel="' . $idName . 1165 '" name="' . $fN . '" value="' . $fV . '" onChange="uFormUrl(' . $aname . ')" />'; 1166 1167 if (empty($this->inlineJavaScript[$typeDat['type']])) { 1168 $this->inlineJavaScript[$typeDat['type']] = 'require([\'TYPO3/CMS/Backend/ColorPicker\'], function(ColorPicker){ColorPicker.initialize()});'; 1169 } 1170 break; 1171 case 'wrap': 1172 $wArr = explode('|', $fV); 1173 $p_field = '<div class="input-group"> 1174 <input class="form-control form-control-adapt" type="text" id="' . $idName . '" name="' . $fN . '" value="' . $wArr[0] . '" onChange="uFormUrl(' . $aname . ')" /> 1175 <span class="input-group-addon input-group-icon">|</span> 1176 <input class="form-control form-control-adapt" type="text" name="W' . $fN . '" value="' . $wArr[1] . '" onChange="uFormUrl(' . $aname . ')" /> 1177 </div>'; 1178 break; 1179 case 'offset': 1180 $wArr = explode(',', $fV); 1181 $labels = GeneralUtility::trimExplode(',', $typeDat['paramstr']); 1182 $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] . '" onChange="uFormUrl(' . $aname . ')" />'; 1183 $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" onChange="uFormUrl(' . $aname . ')" />'; 1184 $labelsCount = count($labels); 1185 for ($aa = 2; $aa < $labelsCount; $aa++) { 1186 if ($labels[$aa]) { 1187 $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" onChange="uFormUrl(' . $aname . ')" />'; 1188 } else { 1189 $p_field .= '<input type="hidden" name="W' . $aa . $fN . '" value="' . $wArr[$aa] . '" />'; 1190 } 1191 } 1192 $p_field = '<div class="input-group">' . $p_field . '</div>'; 1193 break; 1194 case 'options': 1195 if (is_array($typeDat['params'])) { 1196 $p_field = ''; 1197 foreach ($typeDat['params'] as $val) { 1198 $vParts = explode('=', $val, 2); 1199 $label = $vParts[0]; 1200 $val = $vParts[1] ?? $vParts[0]; 1201 // option tag: 1202 $sel = ''; 1203 if ($val === $params['value']) { 1204 $sel = ' selected'; 1205 } 1206 $p_field .= '<option value="' . htmlspecialchars($val) . '"' . $sel . '>' . $this->getLanguageService()->sL($label) . '</option>'; 1207 } 1208 $p_field = '<select class="form-control" id="' . $idName . '" name="' . $fN . '" onChange="uFormUrl(' . $aname . ')">' . $p_field . '</select>'; 1209 } 1210 break; 1211 case 'boolean': 1212 $sel = $fV ? 'checked' : ''; 1213 $p_field = 1214 '<input type="hidden" name="' . $fN . '" value="0" />' 1215 . '<label class="btn btn-default btn-checkbox">' 1216 . '<input id="' . $idName . '" type="checkbox" name="' . $fN . '" value="' . ($typeDat['paramstr'] ? $typeDat['paramstr'] : 1) . '" ' . $sel . ' onClick="uFormUrl(' . $aname . ')" />' 1217 . '<span class="t3-icon fa"></span>' 1218 . '</label>'; 1219 break; 1220 case 'comment': 1221 $sel = $fV ? '' : 'checked'; 1222 $p_field = 1223 '<input type="hidden" name="' . $fN . '" value="" />' 1224 . '<label class="btn btn-default btn-checkbox">' 1225 . '<input id="' . $idName . '" type="checkbox" name="' . $fN . '" value="1" ' . $sel . ' onClick="uFormUrl(' . $aname . ')" />' 1226 . '<span class="t3-icon fa"></span>' 1227 . '</label>'; 1228 break; 1229 case 'file': 1230 // extensionlist 1231 $extList = $typeDat['paramstr']; 1232 if ($extList === 'IMAGE_EXT') { 1233 $extList = $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']; 1234 } 1235 $p_field = '<option value="">(' . $extList . ')</option>'; 1236 if (trim($params['value'])) { 1237 $val = $params['value']; 1238 $p_field .= '<option value=""></option>'; 1239 $p_field .= '<option value="' . htmlspecialchars($val) . '" selected>' . $val . '</option>'; 1240 } 1241 $p_field = '<select class="form-select" id="' . $idName . '" name="' . $fN . '" onChange="uFormUrl(' . $aname . ')">' . $p_field . '</select>'; 1242 break; 1243 case 'user': 1244 $userFunction = $typeDat['paramstr']; 1245 $userFunctionParams = ['fieldName' => $fN, 'fieldValue' => $fV]; 1246 $p_field = GeneralUtility::callUserFunction($userFunction, $userFunctionParams, $this); 1247 break; 1248 default: 1249 $p_field = '<input class="form-control" id="' . $idName . '" type="text" name="' . $fN . '" value="' . $fV . '"' 1250 . ' onChange="uFormUrl(' . $aname . ')" />'; 1251 } 1252 // Define default names and IDs 1253 $userTyposcriptID = 'userTS-' . $idName; 1254 $defaultTyposcriptID = 'defaultTS-' . $idName; 1255 $checkboxName = 'check[' . $params['name'] . ']'; 1256 $checkboxID = 'check-' . $idName; 1257 $userTyposcriptStyle = ''; 1258 $deleteIconHTML = ''; 1259 $constantCheckbox = ''; 1260 $constantDefaultRow = ''; 1261 if (!$this->ext_dontCheckIssetValues) { 1262 // Set the default styling options 1263 if (isset($this->objReg[$params['name']])) { 1264 $checkboxValue = 'checked'; 1265 $defaultTyposcriptStyle = 'style="display:none;"'; 1266 } else { 1267 $checkboxValue = ''; 1268 $userTyposcriptStyle = 'style="display:none;"'; 1269 $defaultTyposcriptStyle = ''; 1270 } 1271 $deleteTitle = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.deleteTitle')); 1272 $deleteIcon = $iconFactory->getIcon('actions-edit-undo', Icon::SIZE_SMALL)->render(); 1273 $deleteIconHTML = 1274 '<button type="button" class="btn btn-default t3js-toggle" data-toggle="undo" rel="' . $idName . '">' 1275 . '<span title="' . $deleteTitle . '" alt="' . $deleteTitle . '">' 1276 . $deleteIcon 1277 . '</span>' 1278 . '</button>'; 1279 $editTitle = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.editTitle')); 1280 $editIcon = $iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render(); 1281 $editIconHTML = 1282 '<button type="button" class="btn btn-default t3js-toggle" data-toggle="edit" rel="' . $idName . '">' 1283 . '<span title="' . $editTitle . '" alt="' . $editTitle . '">' 1284 . $editIcon 1285 . '</span>' 1286 . '</button>'; 1287 $constantCheckbox = '<input type="hidden" name="' . $checkboxName . '" id="' . $checkboxID . '" value="' . $checkboxValue . '"/>'; 1288 // If there's no default value for the field, use a static label. 1289 if (!$params['default_value']) { 1290 $params['default_value'] = '[Empty]'; 1291 } 1292 $constantDefaultRow = 1293 '<div class="input-group defaultTS" id="' . $defaultTyposcriptID . '" ' . $defaultTyposcriptStyle . '>' 1294 . '<span class="input-group-btn">' . $editIconHTML . '</span>' 1295 . '<input class="form-control" type="text" placeholder="' . htmlspecialchars($params['default_value']) . '" readonly>' 1296 . '</div>'; 1297 } 1298 $constantEditRow = 1299 '<div class="input-group userTS" id="' . $userTyposcriptID . '" ' . $userTyposcriptStyle . '>' 1300 . '<span class="input-group-btn">' . $deleteIconHTML . '</span>' 1301 . $p_field 1302 . '</div>'; 1303 $constantLabel = '<label class="t3js-formengine-label"><span>' . htmlspecialchars($head) . '</span></label>'; 1304 $constantName = '<span class="help-block">[' . $params['name'] . ']</span>'; 1305 $constantDescription = $body ? '<p class="help-block">' . htmlspecialchars($body) . '</p>' : ''; 1306 $constantData = ''; 1307 if ($hint !== '') { 1308 $constantData .= '<span class="help-block">' . $hint . '</span>'; 1309 } 1310 $constantData .= 1311 $constantCheckbox 1312 . $constantEditRow 1313 . $constantDefaultRow; 1314 1315 $output .= 1316 '<fieldset class="form-section">' 1317 . '<a name="' . $raname . '"></a>' 1318 . '<div class="form-group">' 1319 . $constantLabel . $constantName . $constantDescription . $constantData 1320 . '</div>' 1321 . '</fieldset>'; 1322 } else { 1323 debug('Error. Constant did not exist. Should not happen.'); 1324 } 1325 } 1326 } 1327 return '<div class="tstemplate-constanteditor">' . $output . '</div>'; 1328 } 1329 1330 /*************************** 1331 * 1332 * Processing input values 1333 * 1334 ***************************/ 1335 /** 1336 * @param string $constants 1337 */ 1338 public function ext_regObjectPositions($constants) 1339 { 1340 // This runs through the lines of the constants-field of the active template and registers the constants-names 1341 // and line positions in an array, $this->objReg 1342 $this->raw = explode(LF, $constants); 1343 $this->rawP = 0; 1344 // Resetting the objReg if the divider is found!! 1345 $this->objReg = []; 1346 $this->ext_regObjects(''); 1347 } 1348 1349 /** 1350 * @param string $pre 1351 */ 1352 public function ext_regObjects($pre) 1353 { 1354 // Works with regObjectPositions. "expands" the names of the TypoScript objects 1355 while (isset($this->raw[$this->rawP])) { 1356 $line = ltrim($this->raw[$this->rawP]); 1357 $this->rawP++; 1358 if ($line) { 1359 if ($line[0] === '[') { 1360 } elseif (strcspn($line, '}#/') != 0) { 1361 $varL = strcspn($line, ' {=<'); 1362 $var = substr($line, 0, $varL); 1363 $line = ltrim(substr($line, $varL)); 1364 switch ($line[0]) { 1365 case '=': 1366 $this->objReg[$pre . $var] = $this->rawP - 1; 1367 break; 1368 case '{': 1369 $this->ext_inBrace++; 1370 $this->ext_regObjects($pre . $var . '.'); 1371 break; 1372 } 1373 $this->lastComment = ''; 1374 } elseif ($line[0] === '}') { 1375 $this->lastComment = ''; 1376 $this->ext_inBrace--; 1377 if ($this->ext_inBrace < 0) { 1378 $this->ext_inBrace = 0; 1379 } else { 1380 break; 1381 } 1382 } 1383 } 1384 } 1385 } 1386 1387 /** 1388 * @param string $key 1389 * @param string $var 1390 */ 1391 public function ext_putValueInConf($key, $var) 1392 { 1393 // Puts the value $var to the TypoScript value $key in the current lines of the templates. 1394 // If the $key is not found in the template constants field, a new line is inserted in the bottom. 1395 $theValue = ' ' . trim($var); 1396 if (isset($this->objReg[$key])) { 1397 $lineNum = $this->objReg[$key]; 1398 $parts = explode('=', $this->raw[$lineNum], 2); 1399 if (count($parts) === 2) { 1400 $parts[1] = $theValue; 1401 } 1402 $this->raw[$lineNum] = implode('=', $parts); 1403 } else { 1404 $this->raw[] = $key . ' =' . $theValue; 1405 } 1406 $this->changed = true; 1407 } 1408 1409 /** 1410 * @param string $key 1411 */ 1412 public function ext_removeValueInConf($key) 1413 { 1414 // Removes the value in the configuration 1415 if (isset($this->objReg[$key])) { 1416 $lineNum = $this->objReg[$key]; 1417 unset($this->raw[$lineNum]); 1418 } 1419 $this->changed = true; 1420 } 1421 1422 /** 1423 * @param array $arr 1424 * @param array $settings 1425 * @return array 1426 */ 1427 public function ext_depthKeys($arr, $settings) 1428 { 1429 $tsbrArray = []; 1430 foreach ($arr as $theK => $theV) { 1431 $theKeyParts = explode('.', $theK); 1432 $depth = ''; 1433 $c = count($theKeyParts); 1434 $a = 0; 1435 foreach ($theKeyParts as $p) { 1436 $a++; 1437 $depth .= ($depth ? '.' : '') . $p; 1438 $tsbrArray[$depth] = $c == $a ? $theV : 1; 1439 } 1440 } 1441 // Modify settings 1442 foreach ($tsbrArray as $theK => $theV) { 1443 if ($theV) { 1444 $settings[$theK] = 1; 1445 } else { 1446 unset($settings[$theK]); 1447 } 1448 } 1449 return $settings; 1450 } 1451 1452 /** 1453 * Process input 1454 * 1455 * @param array $http_post_vars 1456 * @param array $http_post_files (not used anymore) 1457 * @param array $theConstants 1458 * @param array $tplRow Not used 1459 */ 1460 public function ext_procesInput($http_post_vars, $http_post_files, $theConstants, $tplRow) 1461 { 1462 $data = $http_post_vars['data']; 1463 $check = $http_post_vars['check']; 1464 $Wdata = $http_post_vars['Wdata']; 1465 $W2data = $http_post_vars['W2data']; 1466 $W3data = $http_post_vars['W3data']; 1467 $W4data = $http_post_vars['W4data']; 1468 $W5data = $http_post_vars['W5data']; 1469 if (is_array($data)) { 1470 foreach ($data as $key => $var) { 1471 if (isset($theConstants[$key])) { 1472 // If checkbox is set, update the value 1473 if ($this->ext_dontCheckIssetValues || isset($check[$key])) { 1474 // Exploding with linebreak, just to make sure that no multiline input is given! 1475 list($var) = explode(LF, $var); 1476 $typeDat = $this->ext_getTypeData($theConstants[$key]['type']); 1477 switch ($typeDat['type']) { 1478 case 'int': 1479 if ($typeDat['paramstr']) { 1480 $var = MathUtility::forceIntegerInRange($var, $typeDat['params'][0], $typeDat['params'][1]); 1481 } else { 1482 $var = (int)$var; 1483 } 1484 break; 1485 case 'int+': 1486 $var = max(0, (int)$var); 1487 break; 1488 case 'color': 1489 $col = []; 1490 if ($var) { 1491 $var = preg_replace('/[^A-Fa-f0-9]*/', '', $var); 1492 $useFulHex = strlen($var) > 3; 1493 $col[] = hexdec($var[0]); 1494 $col[] = hexdec($var[1]); 1495 $col[] = hexdec($var[2]); 1496 if ($useFulHex) { 1497 $col[] = hexdec($var[3]); 1498 $col[] = hexdec($var[4]); 1499 $col[] = hexdec($var[5]); 1500 } 1501 $var = substr('0' . dechex($col[0]), -1) . substr('0' . dechex($col[1]), -1) . substr('0' . dechex($col[2]), -1); 1502 if ($useFulHex) { 1503 $var .= substr('0' . dechex($col[3]), -1) . substr('0' . dechex($col[4]), -1) . substr('0' . dechex($col[5]), -1); 1504 } 1505 $var = '#' . strtoupper($var); 1506 } 1507 break; 1508 case 'comment': 1509 if ($var) { 1510 $var = ''; 1511 } else { 1512 $var = '#'; 1513 } 1514 break; 1515 case 'wrap': 1516 if (isset($Wdata[$key])) { 1517 $var .= '|' . $Wdata[$key]; 1518 } 1519 break; 1520 case 'offset': 1521 if (isset($Wdata[$key])) { 1522 $var = (int)$var . ',' . (int)$Wdata[$key]; 1523 if (isset($W2data[$key])) { 1524 $var .= ',' . (int)$W2data[$key]; 1525 if (isset($W3data[$key])) { 1526 $var .= ',' . (int)$W3data[$key]; 1527 if (isset($W4data[$key])) { 1528 $var .= ',' . (int)$W4data[$key]; 1529 if (isset($W5data[$key])) { 1530 $var .= ',' . (int)$W5data[$key]; 1531 } 1532 } 1533 } 1534 } 1535 } 1536 break; 1537 case 'boolean': 1538 if ($var) { 1539 $var = $typeDat['paramstr'] ? $typeDat['paramstr'] : 1; 1540 } 1541 break; 1542 } 1543 if ($this->ext_printAll || (string)$theConstants[$key]['value'] !== (string)$var) { 1544 // Put value in, if changed. 1545 $this->ext_putValueInConf($key, $var); 1546 } 1547 // Remove the entry because it has been "used" 1548 unset($check[$key]); 1549 } else { 1550 $this->ext_removeValueInConf($key); 1551 } 1552 } 1553 } 1554 } 1555 // Remaining keys in $check indicates fields that are just clicked "on" to be edited. 1556 // Therefore we get the default value and puts that in the template as a start... 1557 if (!$this->ext_dontCheckIssetValues && is_array($check)) { 1558 foreach ($check as $key => $var) { 1559 if (isset($theConstants[$key])) { 1560 $dValue = $theConstants[$key]['default_value']; 1561 $this->ext_putValueInConf($key, $dValue); 1562 } 1563 } 1564 } 1565 } 1566 1567 /** 1568 * @param int $id 1569 * @param string $perms_clause 1570 * @return array 1571 */ 1572 public function ext_prevPageWithTemplate($id, $perms_clause) 1573 { 1574 $rootLine = BackendUtility::BEgetRootLine($id, $perms_clause ? ' AND ' . $perms_clause : ''); 1575 foreach ($rootLine as $p) { 1576 if ($this->ext_getFirstTemplate($p['uid'])) { 1577 return $p; 1578 } 1579 } 1580 return []; 1581 } 1582 1583 /** 1584 * Is set by runThroughTemplates(), previously set via TemplateAnalyzerModuleFunctionController from the outside 1585 * 1586 * @return array 1587 */ 1588 protected function getRootLine() 1589 { 1590 return is_array($this->absoluteRootLine) ? $this->absoluteRootLine : []; 1591 } 1592 1593 /** 1594 * @return LanguageService 1595 */ 1596 protected function getLanguageService() 1597 { 1598 return $GLOBALS['LANG']; 1599 } 1600 1601 /** 1602 * @return DocumentTemplate 1603 */ 1604 protected function getDocumentTemplate() 1605 { 1606 return $GLOBALS['TBE_TEMPLATE']; 1607 } 1608} 1609