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