637a7b92495746ac453f3689e6f5db0ee926e654
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / TypoScript / ExtendedTemplateService.php
1 <?php
2 namespace 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
17 use TYPO3\CMS\Backend\Template\DocumentTemplate;
18 use TYPO3\CMS\Backend\Utility\BackendUtility;
19 use TYPO3\CMS\Core\Context\Context;
20 use TYPO3\CMS\Core\Database\ConnectionPool;
21 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
22 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
23 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
24 use TYPO3\CMS\Core\Exception;
25 use TYPO3\CMS\Core\Imaging\Icon;
26 use TYPO3\CMS\Core\Imaging\IconFactory;
27 use TYPO3\CMS\Core\Localization\LanguageService;
28 use TYPO3\CMS\Core\Utility\GeneralUtility;
29 use TYPO3\CMS\Core\Utility\MathUtility;
30 use 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 */
38 class 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 $label = '<strong class="text-danger">' . $label . '</strong>';
458 }
459 // The key has matched the search string
460 $label = '<a href="' . htmlspecialchars($aHref) . '" title="' . htmlspecialchars($ln) . '">' . $label . '</a>';
461 }
462 }
463 $HTML .= '<span class="list-tree-label">[' . $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 &lt; and > with &gt;
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 . '&nbsp;&nbsp;';
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(' ', '&nbsp;', 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(' ', '&nbsp;', $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($cArr, '<br />') . '<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="" ' . $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 }