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