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